From b1aca13f1fcf8ae640e0d9a3448af481d0761385 Mon Sep 17 00:00:00 2001 From: benym Date: Wed, 29 Jul 2026 23:56:51 +0800 Subject: [PATCH] feat: prepare beta.11 configuration and native UX --- AGENTS.md | 2 + CHANGELOG.md | 11 + CLAUDE.md | 2 + app/commands/update.ts | 18 +- assets/manifest.json | 2 +- assets/skills-zh/comet-native/SKILL.md | 3 +- .../comet-native/reference/commands.md | 6 +- .../comet/reference/classic-layout.md | 2 +- assets/skills/comet-native/SKILL.md | 3 +- .../skills/comet-native/reference/commands.md | 6 +- .../scripts/comet-native-runtime.mjs | 9267 ++++++++--------- .../skills/comet/reference/classic-layout.md | 2 +- .../comet/scripts/comet-entry-runtime.mjs | 2 +- .../comet/scripts/comet-hook-router.mjs | 1058 +- assets/skills/comet/scripts/comet-runtime.mjs | 6 +- .../specs/native-completion-loop/spec.md | 3 +- .../native-verification-evidence/spec.md | 2 +- .../classic-layout-initialization.ts | 6 +- domains/comet-entry/init-workflow.ts | 23 +- domains/comet-native/native-change.ts | 145 +- domains/comet-native/native-cli.ts | 62 +- .../comet-native/native-controller-trust.ts | 22 +- .../native-creation-authorization.ts | 164 - domains/comet-native/native-review-trust.ts | 2 +- domains/comet-native/native-snapshot.ts | 39 - domains/comet-native/native-types.ts | 11 +- .../native-verification-runtime.ts | 7 +- domains/skill/platform-install.ts | 25 +- domains/workflow-contract/project-config.ts | 8 +- .../comet-native-workflow/instruction.md | 9 +- .../validation/test_native_workflow.py | 4 +- eval/local/tests/conftest.py | 27 +- .../scaffold/test_native_wave_evaluations.py | 19 +- package-lock.json | 4 +- package.json | 2 +- test/app/cli-help.test.ts | 2 +- test/app/init-e2e.test.ts | 6 + test/app/update.test.ts | 92 + .../comet-classic/classic-layout.test.ts | 8 +- .../domains/comet-entry/init-workflow.test.ts | 4 +- .../comet-native/native-change.test.ts | 174 +- test/domains/comet-native/native-cli.test.ts | 132 +- .../comet-native/native-config.test.ts | 4 +- .../native-evidence-transitions.test.ts | 2 +- .../comet-native/native-review-trust.test.ts | 17 +- .../domains/comet-native/native-skill.test.ts | 4 +- .../native-verification-runtime.test.ts | 12 +- .../classic-layout-skill-contract.test.ts | 4 +- test/domains/skill/skills.test.ts | 10 +- .../workflow-contract.test.ts | 2 +- test/helpers/native-controller-trust.ts | 26 - test/repository/native-runtime-assets.test.ts | 18 +- test/repository/release-metadata.test.ts | 2 +- website | 2 +- 54 files changed, 4935 insertions(+), 6560 deletions(-) delete mode 100644 domains/comet-native/native-creation-authorization.ts diff --git a/AGENTS.md b/AGENTS.md index aaa1d038..3a5eb4ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -218,6 +218,8 @@ Changelog写英文 不能够直接修改Superpowers和OpenSpec的原始Skill +除非用户明确同意,否则不得使用 Superpowers 的任何 Skill。 + ## github规范 不能未经过同意直接在github上评论或者提交PR diff --git a/CHANGELOG.md b/CHANGELOG.md index 0858dfb8..5896cbf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to @rpamis/comet will be documented in this file. +## What's Changed [0.4.0-beta.11] - 2026-07-29 + +### Changed + +- **Project configuration defaults**: New Classic configurations default to `classic.artifact_layout: docs`. `comet update` now fills every missing managed Native and Classic setting, choosing `docs/openspec/` unless an existing root-level `openspec/` project must be preserved. +- **Risk-based Native review**: Independent review is required by the actual implementation scope and risk instead of a change-creation signing mode, so ordinary changes can start immediately while high-risk verification remains fail-closed. + +### Removed + +- **Native creation authorization**: `comet native new` no longer requires `--creation-authorization`, and the `signed-v2` creation protocol plus `comet native trust authorize` have been removed. + ## What's Changed [0.4.0-beta.10] - 2026-07-28 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index c8476ea9..bbe079f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -216,6 +216,8 @@ Changelog写英文 不能够直接修改Superpowers和OpenSpec的原始Skill +除非用户明确同意,否则不得使用 Superpowers 的任何 Skill。 + ## github规范 不能未经过同意直接在github上评论或者提交PR diff --git a/app/commands/update.ts b/app/commands/update.ts index 94d2294e..5378baa5 100644 --- a/app/commands/update.ts +++ b/app/commands/update.ts @@ -1340,7 +1340,21 @@ async function updateSingleProject( const reportedInstallMode = targets.every((target) => nativeProject && target.scope === 'project') ? 'copy' : selectedInstallMode; - const classicArtifactLayout = projectConfig?.classic?.artifact_layout ?? 'legacy'; + const rawClassic = projectConfigDocument?.value.classic; + const explicitClassicArtifactLayout = + rawClassic !== null && + typeof rawClassic === 'object' && + !Array.isArray(rawClassic) && + ((rawClassic as Record).artifact_layout === 'legacy' || + (rawClassic as Record).artifact_layout === 'docs') + ? ((rawClassic as Record).artifact_layout as 'legacy' | 'docs') + : null; + const classicArtifactLayout = + explicitClassicArtifactLayout ?? + ((await fileExists(path.join(projectPath, 'openspec'))) && + !(await fileExists(path.join(projectPath, 'docs', 'openspec'))) + ? 'legacy' + : 'docs'); const refreshClassicArtifactRoot = includesProjectScope && classicProject && targets.some((target) => target.scope === 'project'); let classicLayoutInitializationPermit: ClassicLayoutInitializationPermit | undefined; @@ -1777,7 +1791,7 @@ async function updateSingleProject( await mergeProjectConfig( configRoot, languageId ? languageToArtifactLanguage(languageId) : null, - 'legacy', + scope === 'project' ? classicArtifactLayout : 'docs', scope === 'project', scope === 'project' && classicProject, ); diff --git a/assets/manifest.json b/assets/manifest.json index 0ea19684..221b3a93 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -1,5 +1,5 @@ { - "version": "0.4.0-beta.10", + "version": "0.4.0-beta.11", "skills": [ "comet/SKILL.md", "comet-classic/SKILL.md", diff --git a/assets/skills-zh/comet-native/SKILL.md b/assets/skills-zh/comet-native/SKILL.md index da498764..c17aeef5 100644 --- a/assets/skills-zh/comet-native/SKILL.md +++ b/assets/skills-zh/comet-native/SKILL.md @@ -42,11 +42,10 @@ Native 主流程不依赖任何外部 Skill。 ```text comet native new \ - --creation-authorization \ --language zh-CN ``` -如果 Runtime 报告缺少外部授权,停止创建并等待 owner。只使用配置指定的 Native artifact root。 +只使用配置指定的 Native artifact root。 ## Shape diff --git a/assets/skills-zh/comet-native/reference/commands.md b/assets/skills-zh/comet-native/reference/commands.md index 779a425d..c358da4c 100644 --- a/assets/skills-zh/comet-native/reference/commands.md +++ b/assets/skills-zh/comet-native/reference/commands.md @@ -9,7 +9,7 @@ comet native init [--root ] [--language en|zh-CN] comet native root show comet native root move -comet native new --creation-authorization [--language en|zh-CN] +comet native new [--language en|zh-CN] comet native list [--cursor ] comet native show comet native status [--cursor ] @@ -21,8 +21,6 @@ comet native spec rebase --summary `artifact-root` 是项目内相对路径。`new` 在配置缺失时创建默认配置和 `/docs/comet/`。已有配置需要迁移根目录时使用 `root move`,不要直接改配置。 -`new` 需要 owner 提供与该 change 匹配的 creation authorization。当前 Agent 不生成授权、不处理私钥。缺少外部动作时,保留 Runtime 错误并等待 owner。 - `status` 和 `show` 是只读命令。`new` 和 `select` 会建立当前 Native selection。多个候选无法唯一判断时让用户选择。 `status --details` 返回详细 findings 和 acceptance 页: @@ -78,7 +76,7 @@ comet native receipt manual \ ## 外部审核交接 -signed-v2 pass 可能要求 implementation attestation、independent review 或 waiver。当前 Agent 可以准备和最终导入交接产物,但不得执行外部角色的 approve/sign,也不得接收其私钥。 +高风险 change 的 pass 可能要求 implementation attestation、independent review 或 waiver。当前 Agent 可以准备和最终导入交接产物,但不得执行外部角色的 approve/sign,也不得接收其私钥。 Implementation 交接: diff --git a/assets/skills-zh/comet/reference/classic-layout.md b/assets/skills-zh/comet/reference/classic-layout.md index 0ed66a00..8117537b 100644 --- a/assets/skills-zh/comet/reference/classic-layout.md +++ b/assets/skills-zh/comet/reference/classic-layout.md @@ -29,6 +29,6 @@ comet classic root show ## 新旧项目与迁移 - 新 Classic 项目默认使用 `docs/openspec/`。 -- 缺少 `classic.artifact_layout` 的旧项目按 `openspec/` 继续运行。 +- 缺少 `classic.artifact_layout` 时默认使用 `docs/openspec/`;`comet update` 检测到已有根目录 `openspec/` 产物时会显式补为 `legacy`,不会移动产物。 - 普通 init/update 不移动旧产物。迁移前运行 `comet classic root move docs --dry-run` 并记录输出的 plan ID;只有用户明确授权后才运行 `comet classic root move docs --apply --plan `。 - 首版迁移不接受任何 active 或 unmanaged OpenSpec change。 diff --git a/assets/skills/comet-native/SKILL.md b/assets/skills/comet-native/SKILL.md index 36d4fce1..44272343 100644 --- a/assets/skills/comet-native/SKILL.md +++ b/assets/skills/comet-native/SKILL.md @@ -42,11 +42,10 @@ If multiple reasonable candidates remain, ask the user to select one. Create a c ```text comet native new \ - --creation-authorization \ --language en ``` -If the Runtime reports missing external authorization, stop creation and wait for the owner. Use only the Native artifact root selected by project configuration. +Use only the Native artifact root selected by project configuration. ## Shape diff --git a/assets/skills/comet-native/reference/commands.md b/assets/skills/comet-native/reference/commands.md index 5916b4cd..0bf86748 100644 --- a/assets/skills/comet-native/reference/commands.md +++ b/assets/skills/comet-native/reference/commands.md @@ -9,7 +9,7 @@ comet native init [--root ] [--language en|zh-CN] comet native root show comet native root move -comet native new --creation-authorization [--language en|zh-CN] +comet native new [--language en|zh-CN] comet native list [--cursor ] comet native show comet native status [--cursor ] @@ -21,8 +21,6 @@ comet native spec rebase --summary `artifact-root` is project-relative. `new` creates default configuration and `/docs/comet/` when configuration is absent. Use `root move` to migrate an existing root; do not edit configuration directly. -`new` requires a creation authorization supplied by the owner for that change. The current Agent does not generate authorization or handle private keys. If the external action is missing, preserve the Runtime error and wait for the owner. - `status` and `show` are read-only. `new` and `select` establish the current Native selection. Ask the user when multiple candidates cannot be resolved uniquely. `status --details` returns detailed findings and an acceptance page: @@ -78,7 +76,7 @@ Create receipts only for commands or manual observations that actually occurred. ## External review handoff -A signed-v2 pass may require an implementation attestation, independent review, or waiver. The current Agent may prepare and finalize handoff artifacts, but must not perform an external role's approve or sign action or receive its private key. +A high-risk change may require an implementation attestation, independent review, or waiver before it can pass. The current Agent may prepare and finalize handoff artifacts, but must not perform an external role's approve or sign action or receive its private key. Implementation handoff: diff --git a/assets/skills/comet-native/scripts/comet-native-runtime.mjs b/assets/skills/comet-native/scripts/comet-native-runtime.mjs index b01a2f32..37ea7e21 100644 --- a/assets/skills/comet-native/scripts/comet-native-runtime.mjs +++ b/assets/skills/comet-native/scripts/comet-native-runtime.mjs @@ -7629,14 +7629,13 @@ function recordOutcomeWithResolver(pkg, state, outcome, resolver, context) { } // domains/comet-native/native-artifacts.ts -import { promises as fs16 } from "fs"; -import path22 from "path"; +import { promises as fs14 } from "fs"; +import path19 from "path"; // domains/comet-native/native-change.ts var import_yaml2 = __toESM(require_dist(), 1); -import { createHash as createHash12 } from "node:crypto"; -import { promises as fs15 } from "fs"; -import path21 from "path"; +import { promises as fs13 } from "fs"; +import path18 from "path"; // domains/comet-native/native-bounded-file.ts import { createHash as createHash2 } from "node:crypto"; @@ -7857,3481 +7856,2700 @@ async function readNativeBoundedTextFile(options) { } } -// domains/comet-native/native-canonical-hash.ts -import { createHash as createHash3 } from "crypto"; -function invalidCanonicalJson(detail) { - throw new TypeError(`Value is not valid canonical JSON: ${detail}`); +// domains/comet-native/native-atomic-file.ts +import { randomUUID } from "crypto"; +import { promises as fs3 } from "fs"; +import path3 from "path"; +function isInside2(parent, target) { + const relative = path3.relative(parent, target); + return relative === "" || !path3.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path3.sep}`); } -function canonicalArray(value, ancestors) { - if (ancestors.has(value)) invalidCanonicalJson("cyclic structures are not supported"); - ancestors.add(value); - try { - const enumerableKeys = Object.keys(value); - for (let index = 0; index < value.length; index += 1) { - if (!Object.prototype.hasOwnProperty.call(value, index)) { - invalidCanonicalJson("sparse arrays are not supported"); - } - } - if (enumerableKeys.length !== value.length || enumerableKeys.some((key, index) => key !== String(index))) { - invalidCanonicalJson("arrays must not have named enumerable properties"); - } - if (Object.getOwnPropertySymbols(value).length > 0) { - invalidCanonicalJson("symbol properties are not supported"); +function sameDirectoryIdentity2(identity, stat) { + return sameFileObject( + { ...identity, birthtime: identity.birthtimeMs }, + { + ...stat, + birthtime: stat.birthtimeMs } - return `[${value.map((entry2) => canonicalValue(entry2, ancestors)).join(",")}]`; - } finally { - ancestors.delete(value); - } + ); } -function canonicalObject(value, ancestors) { - const prototype = Object.getPrototypeOf(value); - if (prototype !== Object.prototype && prototype !== null) { - invalidCanonicalJson("only plain objects are supported"); - } - if (ancestors.has(value)) invalidCanonicalJson("cyclic structures are not supported"); - if (Object.getOwnPropertySymbols(value).length > 0) { - invalidCanonicalJson("symbol properties are not supported"); - } - ancestors.add(value); - try { - const descriptors = Object.getOwnPropertyDescriptors(value); - const keys = Object.keys(value).sort(); - const fields = keys.map((key) => { - const descriptor = descriptors[key]; - if (!descriptor || !("value" in descriptor)) { - invalidCanonicalJson("accessor properties are not supported"); - } - return `${JSON.stringify(key)}:${canonicalValue(descriptor.value, ancestors)}`; - }); - return `{${fields.join(",")}}`; - } finally { - ancestors.delete(value); +function sameFileIdentity2(left, right) { + const leftObject = { ...left, birthtime: left.birthtimeMs }; + const rightObject = { ...right, birthtime: right.birthtimeMs }; + if (hasComparableFileObject(leftObject, rightObject)) { + return sameFileObject(leftObject, rightObject); } + return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size; } -function canonicalValue(value, ancestors) { - if (value === null) return "null"; - switch (typeof value) { - case "boolean": - return value ? "true" : "false"; - case "string": - return JSON.stringify(value); - case "number": - if (!Number.isFinite(value)) invalidCanonicalJson("numbers must be finite"); - return JSON.stringify(Object.is(value, -0) ? 0 : value); - case "object": - return Array.isArray(value) ? canonicalArray(value, ancestors) : canonicalObject(value, ancestors); - case "bigint": - case "function": - case "symbol": - case "undefined": - return invalidCanonicalJson(`${typeof value} values are not supported`); +async function captureDirectoryIdentity(directory) { + const stat = await fs3.lstat(directory); + if (!stat.isDirectory() || stat.isSymbolicLink()) { + throw new Error(`Native atomic write parent must be a real directory: ${directory}`); } - return invalidCanonicalJson("unsupported value type"); -} -function canonicalJson(value) { - return canonicalValue(value, /* @__PURE__ */ new Set()); + return { + path: directory, + realPath: await fs3.realpath(directory), + dev: stat.dev, + ino: stat.ino, + birthtimeMs: stat.birthtimeMs + }; } -function canonicalHash(tag, value) { - if (tag.length === 0) throw new TypeError("Canonical hash tag must be non-empty"); - if (/[\r\n]/u.test(tag)) { - throw new TypeError("Canonical hash tag must not contain a line break"); +async function verifyDirectoryChain2(chain) { + for (const identity of chain) { + const stat = await fs3.lstat(identity.path); + if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity2(identity, stat) || await fs3.realpath(identity.path) !== identity.realPath) { + throw new Error(`Native atomic write parent changed before commit: ${identity.path}`); + } } - return createHash3("sha256").update(`${tag} -${canonicalJson(value)}`).digest("hex"); } - -// domains/comet-native/native-controller-trust.ts -import { promises as fs4 } from "node:fs"; -import os from "node:os"; -import path4 from "node:path"; - -// platform/fs/trusted-readonly-file.ts -import { constants as fsConstants2, promises as fs3 } from "node:fs"; -import path3 from "node:path"; -function trustedReadonlyPosixFactsIssue(facts) { - if (facts.fileUid === facts.currentUid) { - return "Trusted file must be owned by a different host identity"; - } - if ((facts.fileMode & 18) !== 0 || facts.fileWritable) { - return "Trusted file is writable by the current process"; +async function prepareContainedDirectoryChain(root, directory) { + const lexicalRoot = path3.resolve(root); + const lexicalDirectory = path3.resolve(directory); + if (!isInside2(lexicalRoot, lexicalDirectory)) { + throw new Error(`Native atomic write parent is outside its managed root: ${directory}`); } - if (facts.parents.some( - (parent) => parent.uid === facts.currentUid || (parent.mode & 18) !== 0 || parent.writable - )) { - return "Trusted file parent chain is writable by the current process"; + const chain = [await captureDirectoryIdentity(lexicalRoot)]; + const segments = path3.relative(lexicalRoot, lexicalDirectory).split(path3.sep).filter(Boolean); + let cursor = lexicalRoot; + for (const segment of segments) { + await verifyDirectoryChain2(chain); + cursor = path3.join(cursor, segment); + try { + await fs3.mkdir(cursor); + } catch (error) { + if (error.code !== "EEXIST") throw error; + } + const identity = await captureDirectoryIdentity(cursor); + if (!isInside2(chain[0].realPath, identity.realPath)) { + throw new Error(`Native atomic write parent resolves outside its managed root: ${cursor}`); + } + chain.push(identity); } - return null; + await verifyDirectoryChain2(chain); + return chain; } -var testHostIsolatedFiles = /* @__PURE__ */ new Set(); -function sameIdentity(left, right) { - return left.realPath === right.realPath && left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs; +async function syncDirectory(directory) { + let handle; + try { + handle = await fs3.open(directory, "r"); + await handle.sync(); + } catch (error) { + const code = error.code; + if (!["EACCES", "EBADF", "EINVAL", "EISDIR", "ENOTSUP", "EPERM"].includes(code ?? "")) { + throw error; + } + } finally { + await handle?.close(); + } } -async function currentProcessCanWrite(file) { +async function atomicWrite(file, content, options = {}) { + const directory = path3.dirname(file); + const directoryChain = options.containedRoot ? await prepareContainedDirectoryChain(options.containedRoot, directory) : null; + if (!directoryChain) await fs3.mkdir(directory, { recursive: true }); + const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`); + let handle; + let temporaryIdentity; try { - await fs3.access(file, fsConstants2.W_OK); - return true; + await options.beforeTemporaryOpen?.(); + handle = await fs3.open(temporary, "wx"); + temporaryIdentity = await handle.stat(); + if (directoryChain) { + const [temporaryPathStat, temporaryRealPath] = await Promise.all([ + fs3.lstat(temporary), + fs3.realpath(temporary) + ]); + await verifyDirectoryChain2(directoryChain); + if (!temporaryPathStat.isFile() || temporaryPathStat.isSymbolicLink() || !sameFileIdentity2(temporaryIdentity, temporaryPathStat) || !isInside2(directoryChain[0].realPath, temporaryRealPath)) { + throw new Error("Native atomic write temporary file opened outside its managed parent"); + } + } + if (typeof content === "string") await handle.writeFile(content, "utf8"); + else await handle.writeFile(content); + await handle.sync(); + if (!sameFileIdentity2(temporaryIdentity, await handle.stat())) { + throw new Error("Native atomic write temporary file changed while writing"); + } + await handle.close(); + handle = void 0; + await options.beforeCommit?.(); + if (directoryChain) { + await verifyDirectoryChain2(directoryChain); + const temporaryStat = await fs3.lstat(temporary); + if (!temporaryStat.isFile() || temporaryStat.isSymbolicLink() || !temporaryIdentity || !sameFileIdentity2(temporaryStat, temporaryIdentity)) { + throw new Error("Native atomic write temporary file changed before commit"); + } + } + if (options.exclusive) { + await fs3.link(temporary, file); + await fs3.unlink(temporary); + } else { + await fs3.rename(temporary, file); + } + await syncDirectory(directory); } catch (error) { - if (error.code === "EACCES" || error.code === "EPERM") { - return false; + await handle?.close(); + if (!directoryChain) { + await fs3.rm(temporary, { force: true }); + } else { + try { + await verifyDirectoryChain2(directoryChain); + await fs3.rm(temporary, { force: true }); + } catch { + } } throw error; } } -async function inspectIdentity(file) { - const [stat, realPath] = await Promise.all([fs3.lstat(file), fs3.realpath(file)]); - if (!stat.isFile() || stat.isSymbolicLink()) { - throw new Error("Trusted file capability requires a regular non-symlink file"); - } - return { - realPath, - dev: stat.dev, - ino: stat.ino, - size: stat.size, - mtimeMs: stat.mtimeMs - }; +async function atomicWriteText(file, content, options = {}) { + await atomicWrite(file, content, options); } -async function assertTrustedReadonlyFile(options) { - const identity = await inspectIdentity(options.file); - if (options.previous && !sameIdentity(options.previous, identity)) { - throw new Error("Trusted file identity changed while reading"); - } - if (process.env.NODE_ENV === "test" && testHostIsolatedFiles.has(path3.resolve(options.file))) { - return identity; - } - if (process.platform === "win32") { - throw new Error( - "Trusted file isolation cannot be proven from Windows file mode; use a host read-only mount capability" - ); - } - const currentUid = process.geteuid?.() ?? process.getuid?.(); - if (currentUid === void 0) { - throw new Error("Trusted file owner isolation is unavailable on this platform"); - } - const fileStat = await fs3.stat(identity.realPath); - const parents = []; - let directory = path3.dirname(identity.realPath); - for (; ; ) { - const stat = await fs3.lstat(directory); - if (!stat.isDirectory() || stat.isSymbolicLink()) { - throw new Error("Trusted file parent chain is not a physical directory chain"); - } - parents.push({ - uid: stat.uid, - mode: stat.mode, - writable: await currentProcessCanWrite(directory) - }); - const parent = path3.dirname(directory); - if (parent === directory) break; - directory = parent; - } - const issue = trustedReadonlyPosixFactsIssue({ - currentUid, - fileUid: fileStat.uid, - fileMode: fileStat.mode, - fileWritable: await currentProcessCanWrite(identity.realPath), - parents - }); - if (issue) throw new Error(issue); - return identity; +async function atomicWriteBytes(file, content, options = {}) { + await atomicWrite(file, content, options); +} +async function atomicWriteJson(file, value, options = {}) { + await atomicWriteText(file, JSON.stringify(value, null, 2) + "\n", options); } -// domains/comet-native/native-review-identity.ts -import { - createHash as createHash4, - createPrivateKey, - createPublicKey, - generateKeyPairSync, - sign as cryptoSign, - verify as cryptoVerify -} from "node:crypto"; -var NATIVE_REVIEW_IDENTITY_SCHEMA = "comet.native.review-identity.v1"; -var NATIVE_REVIEW_KEYPAIR_SCHEMA = "comet.native.review-keypair.v1"; -var NATIVE_REVIEW_SIGNATURE_SCHEMA = "comet.native.review-signature.v1"; -var ALGORITHM = "ed25519"; -var HASH_PATTERN = /^[a-f0-9]{64}$/u; -var MAX_PUBLIC_KEY_TEXT = 512; -var MAX_PRIVATE_KEY_TEXT = 16384; -var MAX_SIGNATURE_TEXT = 256; -var SIGNATURE_CONTEXT = Buffer.from("comet.native.review-payload.v1\0", "utf8"); -function nativeReviewPrivateKeyFilePersistenceSupported(platform = process.platform) { - return platform !== "win32"; -} -function record(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); +// domains/comet-native/native-config.ts +import path7 from "path"; + +// domains/workflow-contract/project-config.ts +var import_yaml = __toESM(require_dist(), 1); +import path4 from "path"; +var WORKFLOW_PROJECT_CONFIG_MAX_BYTES = 64 * 1024; +var MAX_WORKFLOW_SNAPSHOT_PATTERN_LENGTH = 1024; +var MAX_WORKFLOW_SNAPSHOT_PATTERN_WILDCARDS = 64; +var DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES = 5; +var DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG = { + include: ["**/*"], + exclude: [], + max_files: 1e4, + max_total_bytes: 256 * 1024 * 1024, + max_duration_ms: 6e4 +}; +var COMMENTS = { + en: { + schema: "# Configuration schema used by Comet. Do not edit this value.", + default_workflow: "# Default workflow entered by /comet. Must also appear in workflows.", + workflows: "# Workflows enabled in this project: native, classic, or both.", + ambient_resume: "# Enables automatic recovery through the read-only Ambient Resume probe for both Native and Classic. Set false to disable it.\n# ambient_resume: true | false", + native: "# Native workflow settings. They do not change Classic state or behavior.", + "native.artifact_root": "# Root directory where Native stores Comet specs, changes, and runtime data.", + "native.language": "# Artifact language used by Native workflow documents.\n# language: en | zh-CN", + "native.clarification_mode": "# Controls whether Native asks one clarification at a time or every currently answerable question in a round.\n# clarification_mode: sequential | batch", + "native.archive_confirmation": "# Controls whether Native archives automatically after a successful preview or waits for explicit user confirmation.\n# archive_confirmation: automatic | required", + "native.max_verify_failures": "# Maximum failed Verify outcomes allowed for one confirmed contract before Native stops the completion loop.", + "native.snapshot": "# Controls the auditable project scope and bounded work used by Native content snapshots.", + "native.snapshot.include": "# Selects the project-relative paths included in Native snapshots. Patterns use / and support *, **, and ?.", + "native.snapshot.exclude": "# Removes paths from the included scope. Exclusions are bound into each new change baseline.", + "native.snapshot.max_files": "# Bounds the number of files captured by one snapshot. Increase it for large monorepos.", + "native.snapshot.max_total_bytes": "# Bounds the total file content hashed by one snapshot. Content is streamed and does not depend on Git hashes.", + "native.snapshot.max_duration_ms": "# Bounds snapshot capture time in milliseconds. Increase it together with the byte budget on slower or larger repositories.", + classic: "# Classic workflow settings. They do not change Native state or behavior.", + "classic.artifact_layout": "# Selects the Classic artifact layout. The default is docs; update preserves detected root-level legacy artifacts.\n# artifact_layout: legacy | docs", + "classic.language": "# Artifact language used by Classic workflow documents.\n# language: en | zh-CN", + "classic.context_compression": "# Controls beta context compression for new Classic changes.\n# context_compression: off | beta", + "classic.review_mode": "# Sets the default review depth for new Classic changes.\n# review_mode: off | standard | thorough", + "classic.auto_transition": "# Automatically enters the next Classic phase after a phase passes.\n# auto_transition: true | false" + }, + "zh-CN": { + schema: "# Comet 使用的配置格式版本,请勿修改此值。", + default_workflow: "# `/comet` 默认进入的工作流;该值也必须出现在 workflows 中。", + workflows: "# 此项目启用的工作流,可填写 native、classic 或同时启用两者。", + ambient_resume: "# 是否启用只读的环境感知恢复探针,同时作用于 Native 和 Classic;设为 false 可关闭自动工作流恢复。\n# ambient_resume: true | false", + native: "# Native 工作流配置,不会改变 Classic 的状态或行为。", + "native.artifact_root": "# Native 产物的存放根目录,包括规格、change 和运行时数据。", + "native.language": "# Native 工作流文档使用的产物语言。\n# 可选值:en | zh-CN", + "native.clarification_mode": "# Native 每轮询问一个问题,或一次提出当前所有可回答的问题。\n# 可选值:sequential | batch", + "native.archive_confirmation": "# Native 归档预演成功后自动归档,或等待用户明确确认。\n# 可选值:automatic | required", + "native.max_verify_failures": "# 同一份已确认 contract 最多允许的 Verify 失败次数;达到上限后停止完成循环。", + "native.snapshot": "# Native 内容快照使用的可审计项目范围与有界工作预算。", + "native.snapshot.include": "# Native 快照纳入的项目相对路径;模式使用 /,支持 *、** 和 ?。", + "native.snapshot.exclude": "# 从纳入范围中排除路径;新 change 会把排除策略绑定到 baseline。", + "native.snapshot.max_files": "# 单次快照最多捕获的文件数;大型 monorepo 可按需提高。", + "native.snapshot.max_total_bytes": "# 单次快照最多哈希的文件内容总字节数;内容采用流式读取,不依赖 Git hash。", + "native.snapshot.max_duration_ms": "# 单次快照的最长执行时间(毫秒);较慢或更大的仓库应与字节预算一并提高。", + classic: "# Classic 工作流配置,不会改变 Native 的状态或行为。", + "classic.artifact_layout": "# Classic 产物布局;默认使用 docs,update 检测到根目录 legacy 产物时予以保留。\n# 可选值:legacy | docs", + "classic.language": "# Classic 工作流文档使用的产物语言。\n# 可选值:en | zh-CN", + "classic.context_compression": "# 新建 Classic change 是否启用 beta 上下文压缩。\n# 可选值:off | beta", + "classic.review_mode": "# 新建 Classic change 默认使用的审查深度。\n# 可选值:off | standard | thorough", + "classic.auto_transition": "# Classic 阶段通过后是否自动进入下一阶段。\n# 可选值:true | false" } - return value; +}; +function projectConfigComment(key, language) { + return COMMENTS[language][key]; } -function exactKeys(value, expected, label) { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - if (JSON.stringify(actual) !== JSON.stringify(canonical)) { - throw new Error(`${label} fields are invalid`); +function commentKey(line, block, nativeNested) { + const match = /^(\s*)([a-z_]+):/u.exec(line); + if (!match) return null; + const indent = match[1].length; + const key = match[2]; + if (indent === 0 && key in COMMENTS.en) return key; + if (indent === 2 && block) { + const blockKey = `${block}.${key}`; + if (blockKey in COMMENTS.en) return blockKey; } -} -function sha256(value) { - return createHash4("sha256").update(value).digest("hex"); -} -function payloadHash(value, label = "Native review payloadHash") { - if (typeof value !== "string" || !HASH_PATTERN.test(value)) { - throw new Error(`${label} must be a lowercase SHA-256 hash`); + if (indent === 4 && block === "native" && nativeNested === "snapshot") { + const nestedKey = `native.snapshot.${key}`; + if (nestedKey in COMMENTS.en) return nestedKey; } - return value; + return null; } -function canonicalBase64(value, label, maxCharacters, expectedBytes) { - if (typeof value !== "string" || value.length === 0 || value.length > maxCharacters || value.length % 4 !== 0) { - throw new Error(`${label} is invalid`); - } - const bytes = Buffer.from(value, "base64"); - if (bytes.length === 0 || bytes.toString("base64") !== value || expectedBytes !== void 0 && bytes.length !== expectedBytes) { - throw new Error(`${label} must use canonical base64`); +function renderStructuredProjectConfig(value, language) { + const output = []; + let block = null; + let nativeNested = null; + for (const line of (0, import_yaml.stringify)(value).trimEnd().split("\n")) { + const key = commentKey(line, block, nativeNested); + if (key) { + const indent = line.match(/^\s*/u)?.[0] ?? ""; + for (const comment of projectConfigComment(key, language).split("\n")) { + output.push(`${indent}${comment}`); + } + } + output.push(line); + if (/^[a-z_]+:/u.test(line)) { + if (line.startsWith("native:")) block = "native"; + else if (line.startsWith("classic:")) block = "classic"; + else block = null; + nativeNested = null; + } else if (/^ {2}[a-z_]+:/u.test(line) && block === "native") { + nativeNested = line.startsWith(" snapshot:") ? "snapshot" : null; + } } - return bytes; + output.push(""); + return output.join("\n"); } -function publicKeyMaterial(value) { - const supplied = canonicalBase64(value, "Native review public key", MAX_PUBLIC_KEY_TEXT); - let key; - try { - key = createPublicKey({ key: supplied, format: "der", type: "spki" }); - } catch (error) { - throw new Error("Native review public key is invalid", { cause: error }); +function projectRelativeSegments(value, label) { + if (typeof value !== "string") throw new Error(`${label} must be a string`); + const trimmed = value.trim(); + if (trimmed.length === 0 || path4.posix.isAbsolute(trimmed) || path4.win32.isAbsolute(trimmed) || /^(?:~|[\\/])/u.test(trimmed)) { + throw new Error(`${label} must be a project-relative path`); } - if (key.type !== "public" || key.asymmetricKeyType !== "ed25519") { - throw new Error("Native review public key must be Ed25519"); + if (trimmed === ".") return []; + const segments = trimmed.replaceAll("\\", "/").split("/"); + if (segments.some((segment) => segment === "..")) { + throw new Error(`${label} must stay inside the project root`); } - const der = key.export({ format: "der", type: "spki" }); - if (!Buffer.isBuffer(der) || !supplied.equals(der)) { - throw new Error("Native review public key must use canonical SPKI DER"); + if (segments.some((segment) => segment === "" || segment === ".")) { + throw new Error(`${label} must not contain empty or dot path segments`); } - return { key, der, text: der.toString("base64") }; + return segments; } -function privateKeyMaterial(value) { - const supplied = canonicalBase64(value, "Native review private key", MAX_PRIVATE_KEY_TEXT); - let key; - try { - key = createPrivateKey({ key: supplied, format: "der", type: "pkcs8" }); - } catch (error) { - throw new Error("Native review private key is invalid", { cause: error }); +function normalizeWorkflowArtifactRoot(value) { + const segments = projectRelativeSegments(value, "native.artifact_root"); + return segments.length === 0 ? "." : segments.join("/"); +} +function normalizeClassicArtifactLayout(value, fallback = "docs") { + const resolved = value ?? fallback; + if (resolved !== "legacy" && resolved !== "docs") { + throw new Error("classic.artifact_layout must be legacy or docs"); } - if (key.type !== "private" || key.asymmetricKeyType !== "ed25519") { - throw new Error("Native review private key must be Ed25519"); + return resolved; +} +function normalizeWorkflowRelativePath(value, label, allowWildcards = false) { + if (typeof value !== "string") throw new Error(`${label} must be a string`); + const trimmed = value.trim().replaceAll("\\", "/"); + if (trimmed.length === 0 || path4.posix.isAbsolute(trimmed) || path4.win32.isAbsolute(trimmed) || /^(?:~|[\\/])/u.test(trimmed)) { + throw new Error(`${label} must be relative to its declared path base`); } - const der = key.export({ format: "der", type: "pkcs8" }); - if (!Buffer.isBuffer(der) || !supplied.equals(der)) { - throw new Error("Native review private key must use canonical PKCS8 DER"); + const segments = trimmed.split("/"); + if (segments.some((segment) => segment === "..")) { + throw new Error(`${label} must stay inside its declared path base`); } - return { key, der }; -} -function signaturePayload(hash8) { - return Buffer.concat([SIGNATURE_CONTEXT, Buffer.from(hash8, "hex")]); -} -function parseNativeReviewSignature(value) { - const root = record(value, "Native review signature"); - exactKeys( - root, - ["schema", "algorithm", "keyId", "payloadHash", "signature"], - "Native review signature" - ); - if (root.schema !== NATIVE_REVIEW_SIGNATURE_SCHEMA || root.algorithm !== ALGORITHM || typeof root.keyId !== "string" || !HASH_PATTERN.test(root.keyId)) { - throw new Error("Native review signature identity is invalid"); + if (segments.some((segment) => segment === "" || segment === ".")) { + throw new Error(`${label} must not contain empty or dot path segments`); } - const hash8 = payloadHash(root.payloadHash); - const signature = canonicalBase64( - root.signature, - "Native review signature", - MAX_SIGNATURE_TEXT, - 64 - ).toString("base64"); - return { - schema: NATIVE_REVIEW_SIGNATURE_SCHEMA, - algorithm: ALGORITHM, - keyId: root.keyId, - payloadHash: hash8, - signature - }; + if (!allowWildcards && /[*?]/u.test(trimmed)) { + throw new Error(`${label} cannot contain wildcards`); + } + return segments.join("/"); } -function parseNativeReviewIdentity(value) { - const root = record(value, "Native review identity"); - exactKeys(root, ["schema", "algorithm", "keyId", "publicKey"], "Native review identity"); - if (root.schema !== NATIVE_REVIEW_IDENTITY_SCHEMA || root.algorithm !== ALGORITHM || typeof root.keyId !== "string" || !HASH_PATTERN.test(root.keyId)) { - throw new Error("Native review identity keyId is invalid"); +function projectConfigRecord(value, label) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`${label} must be a mapping`); } - const publicKey = publicKeyMaterial(root.publicKey); - const keyId = sha256(publicKey.der); - if (root.keyId !== keyId) { - throw new Error("Native review identity keyId does not match its public key"); + return value; +} +function projectConfigLanguage(value, fallback, label) { + const resolved = value ?? fallback; + if (resolved !== "en" && resolved !== "zh-CN") { + throw new Error(`${label} must be en or zh-CN`); } - return { - schema: NATIVE_REVIEW_IDENTITY_SCHEMA, - algorithm: ALGORITHM, - keyId, - publicKey: publicKey.text - }; + return resolved; } -function generateNativeReviewKeyPair() { - const generated = generateKeyPairSync("ed25519"); - const publicKey = generated.publicKey.export({ format: "der", type: "spki" }); - const privateKey = generated.privateKey.export({ format: "der", type: "pkcs8" }); - if (!Buffer.isBuffer(publicKey) || !Buffer.isBuffer(privateKey)) { - throw new Error("Native review key generation returned unsupported key material"); +function normalizeWorkflowSnapshotPattern(value, label) { + if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/") || value.split("/").includes("..")) { + throw new Error(`${label} contains an unsafe pattern`); } - const identity = { - schema: NATIVE_REVIEW_IDENTITY_SCHEMA, - algorithm: ALGORITHM, - keyId: sha256(publicKey), - publicKey: publicKey.toString("base64") - }; - return { - schema: NATIVE_REVIEW_KEYPAIR_SCHEMA, - identity, - privateKey: privateKey.toString("base64") - }; + if (value.length > MAX_WORKFLOW_SNAPSHOT_PATTERN_LENGTH) { + throw new Error(`${label} exceeds ${MAX_WORKFLOW_SNAPSHOT_PATTERN_LENGTH} characters`); + } + let wildcardTokens = 0; + for (let index = 0; index < value.length; index += 1) { + if (value[index] === "?") { + wildcardTokens += 1; + } else if (value[index] === "*") { + wildcardTokens += 1; + if (value[index + 1] === "*") index += 1; + } + } + if (wildcardTokens > MAX_WORKFLOW_SNAPSHOT_PATTERN_WILDCARDS) { + throw new Error( + `${label} contains more than ${MAX_WORKFLOW_SNAPSHOT_PATTERN_WILDCARDS} wildcard tokens` + ); + } + return value; } -function nativeReviewIdentityFromPrivateKey(privateKeyValue) { - const privateKey = privateKeyMaterial(privateKeyValue); - const publicKey = createPublicKey(privateKey.key).export({ format: "der", type: "spki" }); - if (!Buffer.isBuffer(publicKey)) { - throw new Error("Native review private key returned unsupported public key material"); +function workflowSnapshotPatterns(value, label, fallback) { + if (value === void 0) return [...fallback]; + if (!Array.isArray(value)) throw new Error(`${label} contains an unsafe pattern`); + return [ + ...new Set(value.map((pattern) => normalizeWorkflowSnapshotPattern(pattern, label))) + ].sort((left, right) => left.localeCompare(right, "en")); +} +function positiveWorkflowSnapshotInteger(value, fallback, label) { + const resolved = value ?? fallback; + if (!Number.isSafeInteger(resolved) || resolved < 1) { + throw new Error(`${label} must be a positive integer`); + } + return resolved; +} +function normalizeWorkflowSnapshot(value) { + if (value === void 0) { + return { + ...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG, + include: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.include], + exclude: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.exclude] + }; } + const snapshot2 = projectConfigRecord(value, "native.snapshot"); return { - schema: NATIVE_REVIEW_IDENTITY_SCHEMA, - algorithm: ALGORITHM, - keyId: sha256(publicKey), - publicKey: publicKey.toString("base64") + include: workflowSnapshotPatterns( + snapshot2.include, + "native.snapshot.include", + DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.include + ), + exclude: workflowSnapshotPatterns( + snapshot2.exclude, + "native.snapshot.exclude", + DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.exclude + ), + max_files: positiveWorkflowSnapshotInteger( + snapshot2.max_files, + DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.max_files, + "native.snapshot.max_files" + ), + max_total_bytes: positiveWorkflowSnapshotInteger( + snapshot2.max_total_bytes, + DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.max_total_bytes, + "native.snapshot.max_total_bytes" + ), + max_duration_ms: positiveWorkflowSnapshotInteger( + snapshot2.max_duration_ms, + DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.max_duration_ms, + "native.snapshot.max_duration_ms" + ) }; } -function signNativeReviewPayloadHash(options) { - const identity = parseNativeReviewIdentity(options.identity); - const hash8 = payloadHash(options.payloadHash); - const privateKey = privateKeyMaterial(options.privateKey); - const derivedPublicKey = createPublicKey(privateKey.key).export({ format: "der", type: "spki" }); - if (!Buffer.isBuffer(derivedPublicKey) || !derivedPublicKey.equals(Buffer.from(identity.publicKey, "base64"))) { - throw new Error("Native review private key does not match the public identity"); +function normalizeWorkflowPendingRootMove(value) { + if (value === void 0) return void 0; + const pending = projectConfigRecord(value, "native.pending_root_move"); + const id = pending.id; + const from = pending.from_artifact_root; + const to = pending.to_artifact_root; + const stage = pending.stage; + if (typeof id !== "string" || !/^[a-f0-9-]{8,}$/u.test(id)) { + throw new Error("native.pending_root_move.id is invalid"); + } + if (typeof from !== "string" || typeof to !== "string") { + throw new Error("native.pending_root_move roots must be strings"); + } + if (stage !== "copying" && stage !== "ready" && stage !== "switched") { + throw new Error("native.pending_root_move.stage is invalid"); + } + let cleanup; + if (pending.cleanup !== void 0) { + const rawCleanup = projectConfigRecord(pending.cleanup, "native.pending_root_move.cleanup"); + const kind = rawCleanup.kind; + const state = rawCleanup.state; + const manifestHash = rawCleanup.manifest_hash; + if (kind !== "forward-source" && kind !== "restart-staging" && kind !== "rollback-destination" && kind !== "rollback-staging") { + throw new Error("native.pending_root_move.cleanup.kind is invalid"); + } + if (state !== "prepared" && state !== "quarantined" && state !== "deleting") { + throw new Error("native.pending_root_move.cleanup.state is invalid"); + } + if (typeof manifestHash !== "string" || !/^[a-f0-9]{64}$/u.test(manifestHash)) { + throw new Error("native.pending_root_move.cleanup.manifest_hash is invalid"); + } + cleanup = { kind, state, manifestHash }; } return { - schema: NATIVE_REVIEW_SIGNATURE_SCHEMA, - algorithm: ALGORITHM, - keyId: identity.keyId, - payloadHash: hash8, - signature: cryptoSign(null, signaturePayload(hash8), privateKey.key).toString("base64") + id, + fromArtifactRoot: normalizeWorkflowArtifactRoot(from), + toArtifactRoot: normalizeWorkflowArtifactRoot(to), + stage, + ...cleanup ? { cleanup } : {} }; } -function verifyNativeReviewPayloadHash(options) { - const identity = parseNativeReviewIdentity(options.identity); - const hash8 = payloadHash(options.payloadHash); - const proof = parseNativeReviewSignature(options.proof); - if (proof.payloadHash !== hash8) { - throw new Error("Native review signature payloadHash does not match the expected payloadHash"); +function normalizeWorkflowNativeProjectConfig(value) { + const native = projectConfigRecord(value, "native"); + if (typeof native.artifact_root !== "string") { + throw new Error("native.artifact_root must be a string"); } - if (proof.keyId !== identity.keyId) { - throw new Error("Native review signature keyId does not match the public identity"); + const clarificationMode = native.clarification_mode ?? "sequential"; + if (clarificationMode !== "sequential" && clarificationMode !== "batch") { + throw new Error("native.clarification_mode must be sequential or batch"); } - const signature = canonicalBase64( - proof.signature, - "Native review signature", - MAX_SIGNATURE_TEXT, - 64 - ); - const publicKey = publicKeyMaterial(identity.publicKey); - if (!cryptoVerify(null, signaturePayload(hash8), publicKey.key, signature)) { - throw new Error("Native review signature is invalid"); + const archiveConfirmation = native.archive_confirmation ?? "automatic"; + if (archiveConfirmation !== "automatic" && archiveConfirmation !== "required") { + throw new Error("native.archive_confirmation must be automatic or required"); } - return proof; -} - -// domains/comet-native/native-controller-trust.ts -var NATIVE_CONTROLLER_TRUST_STORE_SCHEMA = "comet.native.controller-trust-store.v1"; -var NATIVE_CONTROLLER_TRUST_STORE_TEST_ENV = "COMET_NATIVE_CONTROLLER_TRUST_STORE_TEST_PATH"; -var PROJECT_ROOT_HASH_TAG = "comet.native.controller-project-root.v1"; -var MAX_STORE_BYTES = 256 * 1024; -var HASH_PATTERN2 = /^[a-f0-9]{64}$/u; -var CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; -function record2(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); + const maxVerifyFailures = native.max_verify_failures ?? DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES; + if (!Number.isSafeInteger(maxVerifyFailures) || maxVerifyFailures < 1) { + throw new Error("native.max_verify_failures must be a positive integer"); } - return value; + const pending = normalizeWorkflowPendingRootMove(native.pending_root_move); + return { + artifact_root: normalizeWorkflowArtifactRoot(native.artifact_root), + language: projectConfigLanguage(native.language, "en", "native.language"), + clarification_mode: clarificationMode, + archive_confirmation: archiveConfirmation, + max_verify_failures: maxVerifyFailures, + snapshot: normalizeWorkflowSnapshot(native.snapshot), + ...pending ? { pending_root_move: pending } : {} + }; } -function exactKeys2(value, keys, label) { - const actual = Object.keys(value).sort(); - const expected = [...keys].sort(); - if (JSON.stringify(actual) !== JSON.stringify(expected)) { - throw new Error(`${label} fields are invalid`); +function normalizeWorkflowClassicProjectConfig(value) { + const classic = projectConfigRecord(value, "classic"); + const contextCompression = classic.context_compression ?? "off"; + if (contextCompression !== "off" && contextCompression !== "beta") { + throw new Error("classic.context_compression must be off or beta"); } -} -function isInside2(parent, target) { - const relative = path4.relative(parent, target); - return relative === "" || !path4.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path4.sep}`); -} -function normalizedPhysicalRoot(root) { - const normalized = path4.normalize(root).replaceAll("\\", "/"); - return process.platform === "win32" ? normalized.toLowerCase() : normalized; -} -async function nativeControllerProjectRootHash(projectRoot) { - return canonicalHash( - PROJECT_ROOT_HASH_TAG, - normalizedPhysicalRoot(await fs4.realpath(projectRoot)) - ); -} -function parseNativeControllerTrustStore(value) { - const root = record2(value, "Native controller trust store"); - exactKeys2(root, ["schema", "projects"], "Native controller trust store"); - if (root.schema !== NATIVE_CONTROLLER_TRUST_STORE_SCHEMA || !Array.isArray(root.projects) || root.projects.length === 0 || root.projects.length > 1024) { - throw new Error("Native controller trust store is invalid"); + const reviewMode = classic.review_mode ?? "standard"; + if (reviewMode !== "off" && reviewMode !== "standard" && reviewMode !== "thorough") { + throw new Error("classic.review_mode must be off, standard, or thorough"); } - const projects = root.projects.map((value2, index) => { - const project = record2(value2, `Native controller trust project ${index}`); - exactKeys2( - project, - ["projectRootHash", "controllerIdentity", "legacyChanges"], - `Native controller trust project ${index}` - ); - if (typeof project.projectRootHash !== "string" || !HASH_PATTERN2.test(project.projectRootHash) || !Array.isArray(project.legacyChanges)) { - throw new Error(`Native controller trust project ${index} is invalid`); - } - const legacyChanges = project.legacyChanges.map((change) => { - if (typeof change !== "string" || !CHANGE_NAME_PATTERN.test(change)) { - throw new Error(`Native controller trust project ${index} legacy change is invalid`); - } - return change; - }); - if (JSON.stringify(legacyChanges) !== JSON.stringify( - [...new Set(legacyChanges)].sort((left, right) => left.localeCompare(right, "en")) - )) { - throw new Error(`Native controller trust project ${index} legacy changes must be sorted`); - } - return { - projectRootHash: project.projectRootHash, - controllerIdentity: parseNativeReviewIdentity(project.controllerIdentity), - legacyChanges - }; - }); - if (JSON.stringify(projects.map((project) => project.projectRootHash)) !== JSON.stringify( - [...new Set(projects.map((project) => project.projectRootHash))].sort( - (left, right) => left.localeCompare(right, "en") - ) - )) { - throw new Error("Native controller trust projects must be sorted and unique"); + const autoTransition = classic.auto_transition ?? true; + if (typeof autoTransition !== "boolean") { + throw new Error("classic.auto_transition must be true or false"); } - return { schema: NATIVE_CONTROLLER_TRUST_STORE_SCHEMA, projects }; -} -function nativeControllerTrustStorePath() { - const testPath = process.env.NODE_ENV === "test" ? process.env[NATIVE_CONTROLLER_TRUST_STORE_TEST_ENV] : void 0; - return path4.resolve( - testPath ?? path4.join(os.homedir(), ".comet", "native-controller-trust.json") - ); + return { + artifact_layout: normalizeClassicArtifactLayout(classic.artifact_layout), + language: projectConfigLanguage(classic.language, "zh-CN", "classic.language"), + context_compression: contextCompression, + review_mode: reviewMode, + auto_transition: autoTransition + }; } -async function readNativeControllerTrustProject(projectRoot) { - const storePath = nativeControllerTrustStorePath(); - let physicalProjectRoot; - try { - physicalProjectRoot = await fs4.realpath(projectRoot); - } catch (error) { - throw new Error("Native project root is unavailable for controller trust", { cause: error }); +function normalizeAmbientResume(value) { + const resolved = value ?? true; + if (typeof resolved !== "boolean") { + throw new Error("ambient_resume must be true or false"); } - let trustedIdentity; - try { - trustedIdentity = await assertTrustedReadonlyFile({ file: storePath }); - } catch (error) { - if (error.code === "ENOENT") return null; - throw new Error("Native controller trust store is not host-isolated read-only", { - cause: error - }); + return resolved; +} +function normalizeWorkflowProjectConfig(root, native, classic, ambientResume, options) { + const hasSchema = root.schema !== void 0; + const hasProjectMarker = hasSchema || root.default_workflow !== void 0 || root.workflows !== void 0 || !options.allowPartialProject && root.native !== void 0; + if (!hasProjectMarker) return null; + if (options.allowPartialProject && !hasSchema) return null; + if (root.schema !== "comet.project.v1") { + throw new Error("Unsupported Comet project schema"); } - let result2; - try { - result2 = await readFileRaceSafe(storePath, MAX_STORE_BYTES, { - label: "Native controller trust store", - verify: (_checkpoint, context) => { - if (isInside2(physicalProjectRoot, context.realPath)) { - throw new Error("Native controller trust store must resolve outside the project"); - } - } - }); - } catch (error) { - if (error.code === "ENOENT") return null; - throw error; + if (root.default_workflow !== "native" && root.default_workflow !== "classic") { + throw new Error("default_workflow must be native or classic"); } - try { - await assertTrustedReadonlyFile({ - file: storePath, - previous: trustedIdentity - }); - } catch (error) { - throw new Error("Native controller trust store isolation changed while reading", { - cause: error - }); + const configuredWorkflows = root.workflows ?? [root.default_workflow]; + if (!Array.isArray(configuredWorkflows) || configuredWorkflows.length === 0 || configuredWorkflows.some((workflow) => workflow !== "native" && workflow !== "classic")) { + throw new Error("workflows must contain native and/or classic"); } - let parsed; - try { - parsed = parseNativeControllerTrustStore(JSON.parse(result2.bytes.toString("utf8"))); - } catch (error) { - throw new Error("Native controller trust store is not valid canonical JSON", { - cause: error - }); + const workflows = [...new Set(configuredWorkflows)]; + if (!workflows.includes(root.default_workflow)) { + throw new Error("workflows must include default_workflow"); } - const projectRootHash = await nativeControllerProjectRootHash(projectRoot); - return parsed.projects.find((project) => project.projectRootHash === projectRootHash) ?? null; -} - -// domains/comet-native/native-creation-authorization.ts -var NATIVE_CREATION_AUTHORIZATION_SCHEMA = "comet.native.creation-authorization.v1"; -var HASH_PATTERN3 = /^[a-f0-9]{64}$/u; -var CHANGE_NAME_PATTERN2 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; -function record3(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); + if (workflows.includes("native") && !native) { + throw new Error("native must be a mapping"); } - return value; + return { + schema: "comet.project.v1", + default_workflow: root.default_workflow, + workflows, + ambient_resume: ambientResume, + ...native ? { native } : {}, + ...classic ? { classic } : {} + }; } -function exactKeys3(value, expected, label) { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - if (JSON.stringify(actual) !== JSON.stringify(canonical)) { - throw new Error(`${label} fields are invalid`); +function parseWorkflowProjectConfigDocument(source, options = {}) { + const document = (0, import_yaml.parseDocument)(source, { uniqueKeys: true }); + if (document.errors.length > 0) { + throw new Error(`Invalid .comet/config.yaml: ${document.errors[0].message}`); + } + const parsed = document.toJS(); + if (parsed === null || parsed === void 0) { + return { value: {}, config: null, ambient_resume: true }; + } + if (typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error("Invalid .comet/config.yaml: root must be a mapping"); } + const value = parsed; + const ambientResume = normalizeAmbientResume(value.ambient_resume); + const native = value.native === void 0 ? void 0 : normalizeWorkflowNativeProjectConfig(value.native); + const classic = value.classic === void 0 ? void 0 : normalizeWorkflowClassicProjectConfig(value.classic); + const config = normalizeWorkflowProjectConfig(value, native, classic, ambientResume, { + allowPartialProject: options.allowPartialProject ?? false + }); + return { + value, + config, + ambient_resume: ambientResume, + ...native ? { native } : {}, + ...classic ? { classic } : {} + }; } -function buildNativeCreationAuthorization(input) { - const content = { - schema: NATIVE_CREATION_AUTHORIZATION_SCHEMA, - controllerKeyId: input.controllerIdentity.keyId, - projectRootHash: input.projectRootHash, - policyHash: input.policyHash, - protocol: "signed-v2", - change: input.change, - issuedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString() +function workflowPendingRootMoveValue(pending) { + return { + id: pending.id, + from_artifact_root: pending.fromArtifactRoot, + to_artifact_root: pending.toArtifactRoot, + stage: pending.stage, + ...pending.cleanup ? { + cleanup: { + kind: pending.cleanup.kind, + state: pending.cleanup.state, + manifest_hash: pending.cleanup.manifestHash + } + } : {} }; - const authorizationHash = canonicalHash(NATIVE_CREATION_AUTHORIZATION_SCHEMA, content); - return parseNativeCreationAuthorization({ - ...content, - authorizationHash, - controllerSignature: signNativeReviewPayloadHash({ - identity: input.controllerIdentity, - privateKey: input.controllerPrivateKey, - payloadHash: authorizationHash - }) - }); } -function parseNativeCreationAuthorization(value) { - const root = record3(value, "Native creation authorization"); - exactKeys3( - root, - [ - "schema", - "controllerKeyId", - "projectRootHash", - "policyHash", - "protocol", - "change", - "issuedAt", - "authorizationHash", - "controllerSignature" - ], - "Native creation authorization" - ); - if (root.schema !== NATIVE_CREATION_AUTHORIZATION_SCHEMA || typeof root.controllerKeyId !== "string" || !HASH_PATTERN3.test(root.controllerKeyId) || typeof root.projectRootHash !== "string" || !HASH_PATTERN3.test(root.projectRootHash) || typeof root.policyHash !== "string" || !HASH_PATTERN3.test(root.policyHash) || root.protocol !== "signed-v2" || typeof root.change !== "string" || !CHANGE_NAME_PATTERN2.test(root.change) || typeof root.issuedAt !== "string" || Number.isNaN(Date.parse(root.issuedAt)) || typeof root.authorizationHash !== "string" || !HASH_PATTERN3.test(root.authorizationHash)) { - throw new Error("Native creation authorization is invalid"); - } - const content = { - schema: NATIVE_CREATION_AUTHORIZATION_SCHEMA, - controllerKeyId: root.controllerKeyId, - projectRootHash: root.projectRootHash, - policyHash: root.policyHash, - protocol: "signed-v2", - change: root.change, - issuedAt: root.issuedAt +function workflowProjectConfigManagedValue(config) { + return { + schema: config.schema, + default_workflow: config.default_workflow, + workflows: config.workflows ?? [config.default_workflow], + ambient_resume: config.ambient_resume, + ...config.native ? { + native: { + artifact_root: config.native.artifact_root, + language: config.native.language, + clarification_mode: config.native.clarification_mode, + archive_confirmation: config.native.archive_confirmation, + max_verify_failures: config.native.max_verify_failures, + snapshot: config.native.snapshot, + ...config.native.pending_root_move ? { + pending_root_move: workflowPendingRootMoveValue(config.native.pending_root_move) + } : {} + } + } : {}, + ...config.classic ? { + classic: { + artifact_layout: config.classic.artifact_layout, + language: config.classic.language, + context_compression: config.classic.context_compression, + review_mode: config.classic.review_mode, + auto_transition: config.classic.auto_transition + } + } : {} }; - const authorizationHash = canonicalHash(NATIVE_CREATION_AUTHORIZATION_SCHEMA, content); - if (authorizationHash !== root.authorizationHash) { - throw new Error("Native creation authorization hash mismatch"); - } - const controllerSignature = parseNativeReviewSignature(root.controllerSignature); - if (controllerSignature.keyId !== root.controllerKeyId || controllerSignature.payloadHash !== authorizationHash) { - throw new Error("Native creation authorization signature binding is invalid"); - } - return { ...content, authorizationHash, controllerSignature }; } -async function verifyNativeCreationAuthorization(options) { - const controllerTrust = await readNativeControllerTrustProject(options.paths.projectRoot); - if (!controllerTrust) { - throw new Error("Native project has no controller-owned trust root"); +function optionalRecord(value) { + return value && typeof value === "object" && !Array.isArray(value) ? value : {}; +} +function mergeWorkflowProjectConfigDocument(existing, config) { + const managed = workflowProjectConfigManagedValue(config); + const validated = parseWorkflowProjectConfigDocument((0, import_yaml.stringify)(managed)).config; + if (!validated) throw new Error("Unsupported Comet project schema"); + const output = { + ...existing, + schema: validated.schema, + default_workflow: validated.default_workflow, + workflows: validated.workflows, + ambient_resume: validated.ambient_resume + }; + if (validated.native) { + const existingNative = optionalRecord(existing.native); + const existingSnapshot = optionalRecord(existingNative.snapshot); + const native = { + ...existingNative, + artifact_root: validated.native.artifact_root, + language: validated.native.language, + clarification_mode: validated.native.clarification_mode, + archive_confirmation: validated.native.archive_confirmation, + max_verify_failures: validated.native.max_verify_failures, + snapshot: { + ...existingSnapshot, + ...validated.native.snapshot + } + }; + if (validated.native.pending_root_move) { + const existingPending = optionalRecord(existingNative.pending_root_move); + const pending = workflowPendingRootMoveValue(validated.native.pending_root_move); + const existingCleanup = optionalRecord(existingPending.cleanup); + const managedCleanup = optionalRecord(pending.cleanup); + native.pending_root_move = { + ...existingPending, + ...pending, + ...pending.cleanup ? { cleanup: { ...existingCleanup, ...managedCleanup } } : {} + }; + } else { + delete native.pending_root_move; + } + output.native = native; } - const authorization = parseNativeCreationAuthorization(options.authorization); - const projectRootHash = await nativeControllerProjectRootHash(options.paths.projectRoot); - if (authorization.controllerKeyId !== controllerTrust.controllerIdentity.keyId || authorization.projectRootHash !== projectRootHash || authorization.policyHash !== options.policyHash || authorization.change !== options.change) { - throw new Error("Native creation authorization does not match the trusted project/change"); + if (validated.classic) { + output.classic = { + ...optionalRecord(existing.classic), + artifact_layout: validated.classic.artifact_layout, + language: validated.classic.language, + context_compression: validated.classic.context_compression, + review_mode: validated.classic.review_mode, + auto_transition: validated.classic.auto_transition + }; } - verifyNativeReviewPayloadHash({ - identity: controllerTrust.controllerIdentity, - payloadHash: authorization.authorizationHash, - proof: authorization.controllerSignature - }); - return authorization; + return output; +} +function defaultWorkflowProjectConfig(artifactRoot = "docs", language = "en") { + return { + schema: "comet.project.v1", + default_workflow: "native", + ambient_resume: true, + native: { + artifact_root: normalizeWorkflowArtifactRoot(artifactRoot), + language, + clarification_mode: "sequential", + archive_confirmation: "automatic", + max_verify_failures: DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES, + snapshot: { + ...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG, + include: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.include], + exclude: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.exclude] + } + } + }; } -// domains/comet-native/native-atomic-file.ts -import { randomUUID } from "crypto"; -import { promises as fs5 } from "fs"; +// domains/workflow-contract/project-config-reader.ts +import { createHash as createHash3 } from "crypto"; + +// domains/workflow-contract/protected-project-path.ts +import { promises as fs4 } from "fs"; import path5 from "path"; -function isInside3(parent, target) { - const relative = path5.relative(parent, target); +function isMissingPath(error) { + const code = error?.code; + return code === "ENOENT" || code === "ENOTDIR"; +} +function isInside3(root, target) { + const relative = path5.relative(root, target); return relative === "" || !path5.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path5.sep}`); } -function sameDirectoryIdentity2(identity, stat) { - return sameFileObject( - { ...identity, birthtime: identity.birthtimeMs }, - { - ...stat, - birthtime: stat.birthtimeMs - } - ); -} -function sameFileIdentity2(left, right) { - const leftObject = { ...left, birthtime: left.birthtimeMs }; - const rightObject = { ...right, birthtime: right.birthtimeMs }; - if (hasComparableFileObject(leftObject, rightObject)) { - return sameFileObject(leftObject, rightObject); - } - return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size; -} -async function captureDirectoryIdentity(directory) { - const stat = await fs5.lstat(directory); +async function assertRealProjectRoot(projectRoot, label) { + const lexicalRoot = path5.resolve(projectRoot); + const stat = await fs4.lstat(lexicalRoot); if (!stat.isDirectory() || stat.isSymbolicLink()) { - throw new Error(`Native atomic write parent must be a real directory: ${directory}`); - } - return { - path: directory, - realPath: await fs5.realpath(directory), - dev: stat.dev, - ino: stat.ino, - birthtimeMs: stat.birthtimeMs - }; -} -async function verifyDirectoryChain2(chain) { - for (const identity of chain) { - const stat = await fs5.lstat(identity.path); - if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity2(identity, stat) || await fs5.realpath(identity.path) !== identity.realPath) { - throw new Error(`Native atomic write parent changed before commit: ${identity.path}`); - } + throw new Error(`${label} project root must be a real directory`); } + return fs4.realpath(lexicalRoot); } -async function prepareContainedDirectoryChain(root, directory) { - const lexicalRoot = path5.resolve(root); - const lexicalDirectory = path5.resolve(directory); - if (!isInside3(lexicalRoot, lexicalDirectory)) { - throw new Error(`Native atomic write parent is outside its managed root: ${directory}`); - } - const chain = [await captureDirectoryIdentity(lexicalRoot)]; - const segments = path5.relative(lexicalRoot, lexicalDirectory).split(path5.sep).filter(Boolean); +async function inspectExistingChain(lexicalRoot, realRoot, segments, options) { let cursor = lexicalRoot; - for (const segment of segments) { - await verifyDirectoryChain2(chain); - cursor = path5.join(cursor, segment); + for (let index = 0; index < segments.length; index++) { + cursor = path5.join(cursor, segments[index]); + let stat; try { - await fs5.mkdir(cursor); + stat = await fs4.lstat(cursor); } catch (error) { - if (error.code !== "EEXIST") throw error; + if (isMissingPath(error)) return { exists: false, kind: "missing" }; + throw error; } - const identity = await captureDirectoryIdentity(cursor); - if (!isInside3(chain[0].realPath, identity.realPath)) { - throw new Error(`Native atomic write parent resolves outside its managed root: ${cursor}`); + const display = path5.relative(lexicalRoot, cursor).replaceAll("\\", "/"); + if (stat.isSymbolicLink()) { + throw new Error(`${options.label} crosses a symbolic link or junction at ${display}`); + } + const final = index === segments.length - 1; + if (!final && !stat.isDirectory()) { + throw new Error(`${options.label} ancestor ${display} must be a real directory`); + } + if (final && (options.expected === "file" && !stat.isFile() || options.expected === "directory" && !stat.isDirectory() || options.expected === "any" && !stat.isFile() && !stat.isDirectory())) { + throw new Error(`${options.label} must be a real ${options.expected}`); + } + const realCursor = await fs4.realpath(cursor); + if (!isInside3(realRoot, realCursor)) { + throw new Error(`${options.label} resolves outside the project root`); + } + if (final) { + return { + exists: true, + kind: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "missing" + }; } - chain.push(identity); } - await verifyDirectoryChain2(chain); - return chain; + return { exists: true, kind: "directory" }; } -async function syncDirectory(directory) { - let handle; - try { - handle = await fs5.open(directory, "r"); - await handle.sync(); - } catch (error) { - const code = error.code; - if (!["EACCES", "EBADF", "EINVAL", "EISDIR", "ENOTSUP", "EPERM"].includes(code ?? "")) { - throw error; - } - } finally { - await handle?.close(); +async function inspectProtectedProjectPath(projectRoot, relativePath, options) { + const relative = normalizeWorkflowRelativePath(relativePath, options.label); + const lexicalRoot = path5.resolve(projectRoot); + const realRoot = await assertRealProjectRoot(lexicalRoot, options.label); + const segments = relative.split("/"); + const target = path5.resolve(lexicalRoot, ...segments); + if (!isInside3(lexicalRoot, target)) { + throw new Error(`${options.label} must stay inside the project root`); } + const result2 = await inspectExistingChain(lexicalRoot, realRoot, segments, options); + return { + projectRoot: lexicalRoot, + target, + relative, + exists: result2.exists, + kind: result2.kind + }; } -async function atomicWrite(file, content, options = {}) { - const directory = path5.dirname(file); - const directoryChain = options.containedRoot ? await prepareContainedDirectoryChain(options.containedRoot, directory) : null; - if (!directoryChain) await fs5.mkdir(directory, { recursive: true }); - const temporary = path5.join(directory, `.${path5.basename(file)}.${randomUUID()}.tmp`); - let handle; - let temporaryIdentity; - try { - await options.beforeTemporaryOpen?.(); - handle = await fs5.open(temporary, "wx"); - temporaryIdentity = await handle.stat(); - if (directoryChain) { - const [temporaryPathStat, temporaryRealPath] = await Promise.all([ - fs5.lstat(temporary), - fs5.realpath(temporary) - ]); - await verifyDirectoryChain2(directoryChain); - if (!temporaryPathStat.isFile() || temporaryPathStat.isSymbolicLink() || !sameFileIdentity2(temporaryIdentity, temporaryPathStat) || !isInside3(directoryChain[0].realPath, temporaryRealPath)) { - throw new Error("Native atomic write temporary file opened outside its managed parent"); - } - } - if (typeof content === "string") await handle.writeFile(content, "utf8"); - else await handle.writeFile(content); - await handle.sync(); - if (!sameFileIdentity2(temporaryIdentity, await handle.stat())) { - throw new Error("Native atomic write temporary file changed while writing"); - } - await handle.close(); - handle = void 0; - await options.beforeCommit?.(); - if (directoryChain) { - await verifyDirectoryChain2(directoryChain); - const temporaryStat = await fs5.lstat(temporary); - if (!temporaryStat.isFile() || temporaryStat.isSymbolicLink() || !temporaryIdentity || !sameFileIdentity2(temporaryStat, temporaryIdentity)) { - throw new Error("Native atomic write temporary file changed before commit"); +async function readProtectedProjectFile(projectRoot, relativePath, maxBytes, options) { + const inspection = await inspectProtectedProjectPath(projectRoot, relativePath, { + label: options.label, + expected: "file" + }); + if (!inspection.exists) { + const error = new Error(`${options.label} does not exist`); + error.code = "ENOENT"; + throw error; + } + const realRoot = await assertRealProjectRoot(inspection.projectRoot, options.label); + return readFileRaceSafe(inspection.target, maxBytes, { + ...options, + verify: async (_checkpoint, context) => { + if (!isInside3(realRoot, context.realPath)) { + throw new Error(`${options.label} resolves outside the project root`); } + await inspectExistingChain(inspection.projectRoot, realRoot, inspection.relative.split("/"), { + label: options.label, + expected: "file" + }); } - if (options.exclusive) { - await fs5.link(temporary, file); - await fs5.unlink(temporary); - } else { - await fs5.rename(temporary, file); - } - await syncDirectory(directory); + }); +} + +// domains/workflow-contract/project-config-reader.ts +var WORKFLOW_PROJECT_CONFIG_PATH = ".comet/config.yaml"; +function workflowProjectConfigIdentityEquals(left, right) { + return left.exists === right.exists && left.sha256 === right.sha256; +} +function isMissingProjectConfig(error) { + const code = error?.code; + return code === "ENOENT" || code === "ENOTDIR"; +} +async function readWorkflowProjectConfigDocument(projectRoot, options = {}) { + return (await readWorkflowProjectConfigSnapshot(projectRoot, options)).document; +} +async function readWorkflowProjectConfigBytes(projectRoot) { + try { + return (await readProtectedProjectFile( + projectRoot, + WORKFLOW_PROJECT_CONFIG_PATH, + WORKFLOW_PROJECT_CONFIG_MAX_BYTES, + { label: WORKFLOW_PROJECT_CONFIG_PATH } + )).bytes; } catch (error) { - await handle?.close(); - if (!directoryChain) { - await fs5.rm(temporary, { force: true }); - } else { - try { - await verifyDirectoryChain2(directoryChain); - await fs5.rm(temporary, { force: true }); - } catch { - } - } + if (isMissingProjectConfig(error)) return null; throw error; } } -async function atomicWriteText(file, content, options = {}) { - await atomicWrite(file, content, options); +function projectConfigIdentity(bytes) { + return bytes ? { + exists: true, + sha256: createHash3("sha256").update(bytes).digest("hex") + } : { exists: false, sha256: null }; } -async function atomicWriteBytes(file, content, options = {}) { - await atomicWrite(file, content, options); +async function readWorkflowProjectConfigIdentity(projectRoot) { + return projectConfigIdentity(await readWorkflowProjectConfigBytes(projectRoot)); } -async function atomicWriteJson(file, value, options = {}) { - await atomicWriteText(file, JSON.stringify(value, null, 2) + "\n", options); +async function readWorkflowProjectConfigSnapshot(projectRoot, options = {}) { + const bytes = await readWorkflowProjectConfigBytes(projectRoot); + return { + document: bytes ? parseWorkflowProjectConfigDocument(bytes.toString("utf8"), options) : null, + identity: projectConfigIdentity(bytes) + }; } -// domains/comet-native/native-config.ts -import path9 from "path"; +// domains/workflow-contract/project-config-transaction.ts +var CONFIG_TRANSACTION_MAX_BYTES = 16 * 1024; -// domains/workflow-contract/project-config.ts -var import_yaml = __toESM(require_dist(), 1); +// domains/workflow-contract/project-config-writer.ts +async function assertWorkflowProjectConfigIdentity(projectRoot, expectedIdentity) { + if (!expectedIdentity) return; + const current = await readWorkflowProjectConfigIdentity(projectRoot); + if (!workflowProjectConfigIdentityEquals(current, expectedIdentity)) { + throw new Error("Project config changed before commit; rerun the operation"); + } +} + +// domains/comet-native/native-paths.ts +import { promises as fs5 } from "fs"; import path6 from "path"; -var WORKFLOW_PROJECT_CONFIG_MAX_BYTES = 64 * 1024; -var MAX_WORKFLOW_SNAPSHOT_PATTERN_LENGTH = 1024; -var MAX_WORKFLOW_SNAPSHOT_PATTERN_WILDCARDS = 64; -var DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES = 5; -var DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG = { - include: ["**/*"], - exclude: [], - max_files: 1e4, - max_total_bytes: 256 * 1024 * 1024, - max_duration_ms: 6e4 -}; -var COMMENTS = { - en: { - schema: "# Configuration schema used by Comet. Do not edit this value.", - default_workflow: "# Default workflow entered by /comet. Must also appear in workflows.", - workflows: "# Workflows enabled in this project: native, classic, or both.", - ambient_resume: "# Enables automatic recovery through the read-only Ambient Resume probe for both Native and Classic. Set false to disable it.\n# ambient_resume: true | false", - native: "# Native workflow settings. They do not change Classic state or behavior.", - "native.artifact_root": "# Root directory where Native stores Comet specs, changes, and runtime data.", - "native.language": "# Artifact language used by Native workflow documents.\n# language: en | zh-CN", - "native.clarification_mode": "# Controls whether Native asks one clarification at a time or every currently answerable question in a round.\n# clarification_mode: sequential | batch", - "native.archive_confirmation": "# Controls whether Native archives automatically after a successful preview or waits for explicit user confirmation.\n# archive_confirmation: automatic | required", - "native.max_verify_failures": "# Maximum failed Verify outcomes allowed for one confirmed contract before Native stops the completion loop.", - "native.snapshot": "# Controls the auditable project scope and bounded work used by Native content snapshots.", - "native.snapshot.include": "# Selects the project-relative paths included in Native snapshots. Patterns use / and support *, **, and ?.", - "native.snapshot.exclude": "# Removes paths from the included scope. Exclusions are bound into each new change baseline.", - "native.snapshot.max_files": "# Bounds the number of files captured by one snapshot. Increase it for large monorepos.", - "native.snapshot.max_total_bytes": "# Bounds the total file content hashed by one snapshot. Content is streamed and does not depend on Git hashes.", - "native.snapshot.max_duration_ms": "# Bounds snapshot capture time in milliseconds. Increase it together with the byte budget on slower or larger repositories.", - classic: "# Classic workflow settings. They do not change Native state or behavior.", - "classic.artifact_layout": "# Selects the Classic artifact layout. New projects use docs; existing projects remain legacy until explicitly migrated.\n# artifact_layout: legacy | docs", - "classic.language": "# Artifact language used by Classic workflow documents.\n# language: en | zh-CN", - "classic.context_compression": "# Controls beta context compression for new Classic changes.\n# context_compression: off | beta", - "classic.review_mode": "# Sets the default review depth for new Classic changes.\n# review_mode: off | standard | thorough", - "classic.auto_transition": "# Automatically enters the next Classic phase after a phase passes.\n# auto_transition: true | false" - }, - "zh-CN": { - schema: "# Comet 使用的配置格式版本,请勿修改此值。", - default_workflow: "# `/comet` 默认进入的工作流;该值也必须出现在 workflows 中。", - workflows: "# 此项目启用的工作流,可填写 native、classic 或同时启用两者。", - ambient_resume: "# 是否启用只读的环境感知恢复探针,同时作用于 Native 和 Classic;设为 false 可关闭自动工作流恢复。\n# ambient_resume: true | false", - native: "# Native 工作流配置,不会改变 Classic 的状态或行为。", - "native.artifact_root": "# Native 产物的存放根目录,包括规格、change 和运行时数据。", - "native.language": "# Native 工作流文档使用的产物语言。\n# 可选值:en | zh-CN", - "native.clarification_mode": "# Native 每轮询问一个问题,或一次提出当前所有可回答的问题。\n# 可选值:sequential | batch", - "native.archive_confirmation": "# Native 归档预演成功后自动归档,或等待用户明确确认。\n# 可选值:automatic | required", - "native.max_verify_failures": "# 同一份已确认 contract 最多允许的 Verify 失败次数;达到上限后停止完成循环。", - "native.snapshot": "# Native 内容快照使用的可审计项目范围与有界工作预算。", - "native.snapshot.include": "# Native 快照纳入的项目相对路径;模式使用 /,支持 *、** 和 ?。", - "native.snapshot.exclude": "# 从纳入范围中排除路径;新 change 会把排除策略绑定到 baseline。", - "native.snapshot.max_files": "# 单次快照最多捕获的文件数;大型 monorepo 可按需提高。", - "native.snapshot.max_total_bytes": "# 单次快照最多哈希的文件内容总字节数;内容采用流式读取,不依赖 Git hash。", - "native.snapshot.max_duration_ms": "# 单次快照的最长执行时间(毫秒);较慢或更大的仓库应与字节预算一并提高。", - classic: "# Classic 工作流配置,不会改变 Native 的状态或行为。", - "classic.artifact_layout": "# Classic 产物布局;新项目使用 docs,已有项目在显式迁移前保持 legacy。\n# 可选值:legacy | docs", - "classic.language": "# Classic 工作流文档使用的产物语言。\n# 可选值:en | zh-CN", - "classic.context_compression": "# 新建 Classic change 是否启用 beta 上下文压缩。\n# 可选值:off | beta", - "classic.review_mode": "# 新建 Classic change 默认使用的审查深度。\n# 可选值:off | standard | thorough", - "classic.auto_transition": "# Classic 阶段通过后是否自动进入下一阶段。\n# 可选值:true | false" +import os from "os"; +var PROJECT_CONFIG_FILE = ".comet/config.yaml"; +async function isFileOrDirectory(target) { + try { + await fs5.lstat(target); + return true; + } catch (error) { + if (error.code === "ENOENT") return false; + throw error; } -}; -function projectConfigComment(key, language) { - return COMMENTS[language][key]; } -function commentKey(line, block, nativeNested) { - const match = /^(\s*)([a-z_]+):/u.exec(line); - if (!match) return null; - const indent = match[1].length; - const key = match[2]; - if (indent === 0 && key in COMMENTS.en) return key; - if (indent === 2 && block) { - const blockKey = `${block}.${key}`; - if (blockKey in COMMENTS.en) return blockKey; +async function declaresNativeProjectConfig(target) { + try { + const source = await fs5.readFile(target, "utf8"); + return /^schema:\s*comet\.project\.v1\s*$/mu.test(source); + } catch (error) { + if (error.code === "ENOENT") return false; + throw error; } - if (indent === 4 && block === "native" && nativeNested === "snapshot") { - const nestedKey = `native.snapshot.${key}`; - if (nestedKey in COMMENTS.en) return nestedKey; +} +function inside(parent, target) { + const relative = path6.relative(parent, target); + return relative === "" || !path6.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path6.sep}`); +} +async function physicalPath(target) { + const missing = []; + let cursor = target; + while (!await isFileOrDirectory(cursor)) { + const parent = path6.dirname(cursor); + if (parent === cursor) break; + missing.push(path6.basename(cursor)); + cursor = parent; } - return null; + const existing = await fs5.realpath(cursor); + return path6.resolve(existing, ...missing.reverse()); } -function renderStructuredProjectConfig(value, language) { - const output = []; - let block = null; - let nativeNested = null; - for (const line of (0, import_yaml.stringify)(value).trimEnd().split("\n")) { - const key = commentKey(line, block, nativeNested); - if (key) { - const indent = line.match(/^\s*/u)?.[0] ?? ""; - for (const comment of projectConfigComment(key, language).split("\n")) { - output.push(`${indent}${comment}`); +async function isSymbolicLink(target) { + try { + return (await fs5.lstat(target)).isSymbolicLink(); + } catch (error) { + if (error.code === "ENOENT") return false; + throw error; + } +} +async function discoverNativeProject(startPath) { + let cursor = path6.resolve(startPath); + try { + if (!(await fs5.stat(cursor)).isDirectory()) cursor = path6.dirname(cursor); + } catch (error) { + if (error.code !== "ENOENT") throw error; + } + const fallback = cursor; + const home = path6.resolve(os.homedir()); + while (true) { + const isHomeBoundary = cursor === home && fallback !== home; + if (!isHomeBoundary) { + const configFile = path6.join(cursor, ...PROJECT_CONFIG_FILE.split("/")); + const configMarksProject = cursor === fallback || await declaresNativeProjectConfig(configFile); + if (await isFileOrDirectory(configFile) && configMarksProject) { + return cursor; } } - output.push(line); - if (/^[a-z_]+:/u.test(line)) { - if (line.startsWith("native:")) block = "native"; - else if (line.startsWith("classic:")) block = "classic"; - else block = null; - nativeNested = null; - } else if (/^ {2}[a-z_]+:/u.test(line) && block === "native") { - nativeNested = line.startsWith(" snapshot:") ? "snapshot" : null; - } + if (await isFileOrDirectory(path6.join(cursor, ".git"))) return cursor; + const parent = path6.dirname(cursor); + if (parent === cursor) return fallback; + cursor = parent; } - output.push(""); - return output.join("\n"); } -function projectRelativeSegments(value, label) { - if (typeof value !== "string") throw new Error(`${label} must be a string`); - const trimmed = value.trim(); - if (trimmed.length === 0 || path6.posix.isAbsolute(trimmed) || path6.win32.isAbsolute(trimmed) || /^(?:~|[\\/])/u.test(trimmed)) { - throw new Error(`${label} must be a project-relative path`); +function normalizeArtifactRootRef(value) { + return normalizeWorkflowArtifactRoot(value); +} +async function resolveArtifactRoot(projectRoot, value) { + const normalized = normalizeArtifactRootRef(value); + const lexical = path6.resolve(projectRoot, ...normalized.split("/")); + const physicalProject = await fs5.realpath(projectRoot); + const physicalTarget = await physicalPath(lexical); + if (!inside(physicalProject, physicalTarget)) { + throw new Error("native.artifact_root resolves outside the project root"); } - if (trimmed === ".") return []; - const segments = trimmed.replaceAll("\\", "/").split("/"); - if (segments.some((segment) => segment === "..")) { - throw new Error(`${label} must stay inside the project root`); + return lexical; +} +async function nativeProjectPaths(projectRoot, artifactRootRef) { + const normalized = normalizeArtifactRootRef(artifactRootRef); + const artifactRoot = await resolveArtifactRoot(projectRoot, normalized); + const nativeRoot = path6.join(artifactRoot, "comet"); + if (await isSymbolicLink(nativeRoot)) { + throw new Error("The configured Native comet root must not be a symbolic link"); } - if (segments.some((segment) => segment === "" || segment === ".")) { - throw new Error(`${label} must not contain empty or dot path segments`); + const [physicalArtifactRoot, physicalNativeRoot] = await Promise.all([ + physicalPath(artifactRoot), + physicalPath(nativeRoot) + ]); + if (!inside(physicalArtifactRoot, physicalNativeRoot)) { + throw new Error("The configured Native comet root resolves outside its artifact root"); } - return segments; + return { + projectRoot: path6.resolve(projectRoot), + configFile: path6.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/")), + artifactRoot, + artifactRootRef: normalized, + nativeRoot, + specsDir: path6.join(nativeRoot, "specs"), + changesDir: path6.join(nativeRoot, "changes"), + archiveDir: path6.join(nativeRoot, "archive"), + runtimeDir: path6.join(nativeRoot, "runtime"), + locksDir: path6.join(nativeRoot, "runtime", "locks"), + transactionsDir: path6.join(nativeRoot, "runtime", "transactions") + }; } -function normalizeWorkflowArtifactRoot(value) { - const segments = projectRelativeSegments(value, "native.artifact_root"); - return segments.length === 0 ? "." : segments.join("/"); +async function ensureNativeDirectories(paths) { + const directories = [ + paths.specsDir, + paths.changesDir, + paths.archiveDir, + paths.locksDir, + paths.transactionsDir + ]; + await Promise.all( + directories.map(async (directory) => { + await resolveContainedNativePath(paths.nativeRoot, directory); + await fs5.mkdir(directory, { recursive: true }); + }) + ); } -function normalizeClassicArtifactLayout(value, fallback = "legacy") { - const resolved = value ?? fallback; - if (resolved !== "legacy" && resolved !== "docs") { - throw new Error("classic.artifact_layout must be legacy or docs"); - } - return resolved; +function isInsidePath(parent, target) { + return inside(path6.resolve(parent), path6.resolve(target)); } -function normalizeWorkflowRelativePath(value, label, allowWildcards = false) { - if (typeof value !== "string") throw new Error(`${label} must be a string`); - const trimmed = value.trim().replaceAll("\\", "/"); - if (trimmed.length === 0 || path6.posix.isAbsolute(trimmed) || path6.win32.isAbsolute(trimmed) || /^(?:~|[\\/])/u.test(trimmed)) { - throw new Error(`${label} must be relative to its declared path base`); - } - const segments = trimmed.split("/"); - if (segments.some((segment) => segment === "..")) { - throw new Error(`${label} must stay inside its declared path base`); +async function resolveContainedNativePath(root, target) { + const lexicalRoot = path6.resolve(root); + const lexicalTarget = path6.resolve(target); + if (!inside(lexicalRoot, lexicalTarget)) { + throw new Error(`Path is outside the Native root: ${target}`); } - if (segments.some((segment) => segment === "" || segment === ".")) { - throw new Error(`${label} must not contain empty or dot path segments`); + if (await isSymbolicLink(lexicalRoot)) { + throw new Error(`Native root must not be a symbolic link: ${root}`); } - if (!allowWildcards && /[*?]/u.test(trimmed)) { - throw new Error(`${label} cannot contain wildcards`); + const [physicalRoot, physicalTarget] = await Promise.all([ + physicalPath(lexicalRoot), + physicalPath(lexicalTarget) + ]); + if (!inside(physicalRoot, physicalTarget)) { + throw new Error(`Path resolves outside the Native root: ${target}`); } - return segments.join("/"); + return lexicalTarget; } -function projectConfigRecord(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be a mapping`); - } - return value; + +// domains/comet-native/native-config.ts +var DEFAULT_NATIVE_SNAPSHOT_CONFIG = DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG; +var DEFAULT_NATIVE_MAX_VERIFY_FAILURES = DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES; +var normalizeNativeSnapshotPattern = normalizeWorkflowSnapshotPattern; +function defaultProjectConfig(artifactRoot = "docs", language = "en") { + return defaultWorkflowProjectConfig(artifactRoot, language); } -function projectConfigLanguage(value, fallback, label) { - const resolved = value ?? fallback; - if (resolved !== "en" && resolved !== "zh-CN") { - throw new Error(`${label} must be en or zh-CN`); - } - return resolved; +async function readProjectConfig(projectRoot) { + const config = (await readWorkflowProjectConfigDocument(projectRoot))?.config ?? null; + if (!config?.native) return null; + return config; } -function normalizeWorkflowSnapshotPattern(value, label) { - if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.startsWith("/") || value.split("/").includes("..")) { - throw new Error(`${label} contains an unsafe pattern`); - } - if (value.length > MAX_WORKFLOW_SNAPSHOT_PATTERN_LENGTH) { - throw new Error(`${label} exceeds ${MAX_WORKFLOW_SNAPSHOT_PATTERN_LENGTH} characters`); +async function assertNoPendingNativeRootMove(projectRoot) { + const config = await readProjectConfig(projectRoot); + if (config?.native.pending_root_move) { + throw new Error( + `Native root move ${config.native.pending_root_move.id} is incomplete; use comet native doctor --repair` + ); } - let wildcardTokens = 0; - for (let index = 0; index < value.length; index += 1) { - if (value[index] === "?") { - wildcardTokens += 1; - } else if (value[index] === "*") { - wildcardTokens += 1; - if (value[index + 1] === "*") index += 1; +} +async function writeProjectConfig(projectRoot, config, options = {}) { + const snapshot2 = await readWorkflowProjectConfigSnapshot(projectRoot, { + allowPartialProject: true + }); + const document = mergeWorkflowProjectConfigDocument(snapshot2.document?.value ?? {}, config); + const canonical = path7.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/")); + await atomicWriteText( + canonical, + renderStructuredProjectConfig(document, config.native.language === "zh-CN" ? "zh-CN" : "en"), + { + containedRoot: projectRoot, + beforeCommit: async () => { + await options.beforeCommit?.(); + await assertWorkflowProjectConfigIdentity(projectRoot, snapshot2.identity); + } } + ); +} +async function resolveNativeProject(options) { + const projectRoot = await discoverNativeProject(options.startPath); + const existing = await readProjectConfig(projectRoot); + if (!existing && options.allowMissingConfig === false) { + throw new Error(`${PROJECT_CONFIG_FILE} was not found`); } - if (wildcardTokens > MAX_WORKFLOW_SNAPSHOT_PATTERN_WILDCARDS) { + if (existing?.native.pending_root_move) { throw new Error( - `${label} contains more than ${MAX_WORKFLOW_SNAPSHOT_PATTERN_WILDCARDS} wildcard tokens` + `Native root move ${existing.native.pending_root_move.id} is incomplete; use comet native doctor --repair` ); } - return value; + const explicit = options.explicitArtifactRoot ? normalizeArtifactRootRef(options.explicitArtifactRoot) : void 0; + if (existing && explicit && explicit !== existing.native.artifact_root) { + throw new Error( + `Configured Native artifact root is ${existing.native.artifact_root}; refusing conflicting root ${explicit}` + ); + } + const config = existing ?? defaultProjectConfig(explicit ?? "docs"); + const paths = await nativeProjectPaths(projectRoot, config.native.artifact_root); + return { config, paths, configured: existing !== null }; } -function workflowSnapshotPatterns(value, label, fallback) { - if (value === void 0) return [...fallback]; - if (!Array.isArray(value)) throw new Error(`${label} contains an unsafe pattern`); - return [ - ...new Set(value.map((pattern) => normalizeWorkflowSnapshotPattern(pattern, label))) - ].sort((left, right) => left.localeCompare(right, "en")); + +// domains/comet-native/native-mutation-lock.ts +import { promises as fs9 } from "fs"; +import path11 from "path"; + +// domains/comet-native/native-lock.ts +import { AsyncLocalStorage } from "async_hooks"; +import { randomUUID as randomUUID2 } from "crypto"; +import { promises as fs6 } from "fs"; +import os2 from "os"; +import path8 from "path"; +var NATIVE_LOCK_MAX_BYTES = 16 * 1024; +var NATIVE_LOCK_COORDINATOR_DIR = ".coordinator"; +var NATIVE_LOCK_COORDINATOR_TIMEOUT_MS = 5e3; +var nativeLockCoordinator = new AsyncLocalStorage(); +var nativeLockLocalCoordinator = /* @__PURE__ */ new Map(); +function lockName(value) { + if (!/^[a-z][a-z0-9-]*$/u.test(value)) throw new Error(`Invalid Native lock name: ${value}`); + return `${value}.lock`; } -function positiveWorkflowSnapshotInteger(value, fallback, label) { - const resolved = value ?? fallback; - if (!Number.isSafeInteger(resolved) || resolved < 1) { - throw new Error(`${label} must be a positive integer`); +function parseNativeLockOwner(value, file) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`Invalid Native lock metadata: ${file}`); } - return resolved; -} -function normalizeWorkflowSnapshot(value) { - if (value === void 0) { - return { - ...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG, - include: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.include], - exclude: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.exclude] - }; + const owner = value; + if (typeof owner.id !== "string" || owner.id.length === 0 || typeof owner.pid !== "number" || !Number.isSafeInteger(owner.pid) || owner.pid < 1 || typeof owner.hostname !== "string" || owner.hostname.length === 0 || typeof owner.createdAt !== "string" || owner.createdAt.length === 0 || typeof owner.operation !== "string" || owner.operation.length === 0) { + throw new Error(`Invalid Native lock metadata: ${file}`); } - const snapshot2 = projectConfigRecord(value, "native.snapshot"); + return owner; +} +function nativeLockFileIdentity(stat) { return { - include: workflowSnapshotPatterns( - snapshot2.include, - "native.snapshot.include", - DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.include - ), - exclude: workflowSnapshotPatterns( - snapshot2.exclude, - "native.snapshot.exclude", - DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.exclude - ), - max_files: positiveWorkflowSnapshotInteger( - snapshot2.max_files, - DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.max_files, - "native.snapshot.max_files" - ), - max_total_bytes: positiveWorkflowSnapshotInteger( - snapshot2.max_total_bytes, - DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.max_total_bytes, - "native.snapshot.max_total_bytes" - ), - max_duration_ms: positiveWorkflowSnapshotInteger( - snapshot2.max_duration_ms, - DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.max_duration_ms, - "native.snapshot.max_duration_ms" - ) + device: stat.dev.toString(), + inode: stat.ino.toString(), + size: stat.size.toString(), + birthtimeNs: stat.birthtimeNs.toString(), + ctimeNs: stat.ctimeNs.toString(), + mtimeNs: stat.mtimeNs.toString() }; } -function normalizeWorkflowPendingRootMove(value) { - if (value === void 0) return void 0; - const pending = projectConfigRecord(value, "native.pending_root_move"); - const id = pending.id; - const from = pending.from_artifact_root; - const to = pending.to_artifact_root; - const stage = pending.stage; - if (typeof id !== "string" || !/^[a-f0-9-]{8,}$/u.test(id)) { - throw new Error("native.pending_root_move.id is invalid"); - } - if (typeof from !== "string" || typeof to !== "string") { - throw new Error("native.pending_root_move roots must be strings"); +function sameNativeLockObject(left, right) { + const leftObject = { dev: left.device, ino: left.inode, birthtime: left.birthtimeNs }; + const rightObject = { dev: right.device, ino: right.inode, birthtime: right.birthtimeNs }; + if (hasComparableFileObject(leftObject, rightObject)) { + return sameFileObject(leftObject, rightObject); } - if (stage !== "copying" && stage !== "ready" && stage !== "switched") { - throw new Error("native.pending_root_move.stage is invalid"); + return sameFileObject(leftObject, rightObject) && left.size === right.size; +} +function sameNativeLockVersion(left, right) { + return sameNativeLockObject(left, right) && left.size === right.size && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs; +} +function sameNativeLockDiagnosis(left, right) { + if (left.status !== right.status) return false; + if (!left.owner || !left.identity || !right.owner || !right.identity) { + return left.owner === right.owner && left.identity === right.identity; } - let cleanup; - if (pending.cleanup !== void 0) { - const rawCleanup = projectConfigRecord(pending.cleanup, "native.pending_root_move.cleanup"); - const kind = rawCleanup.kind; - const state = rawCleanup.state; - const manifestHash = rawCleanup.manifest_hash; - if (kind !== "forward-source" && kind !== "restart-staging" && kind !== "rollback-destination" && kind !== "rollback-staging") { - throw new Error("native.pending_root_move.cleanup.kind is invalid"); - } - if (state !== "prepared" && state !== "quarantined" && state !== "deleting") { - throw new Error("native.pending_root_move.cleanup.state is invalid"); - } - if (typeof manifestHash !== "string" || !/^[a-f0-9]{64}$/u.test(manifestHash)) { - throw new Error("native.pending_root_move.cleanup.manifest_hash is invalid"); + return left.owner.id === right.owner.id && sameNativeLockVersion(left.identity, right.identity); +} +async function readNativeLockSnapshot(file) { + let bytes; + let stat; + try { + const result2 = await readFileRaceSafe(file, NATIVE_LOCK_MAX_BYTES, { + bigint: true, + label: "Native lock" + }); + bytes = result2.bytes; + stat = result2.stat; + } catch (error) { + if (error.code === "ENOENT") return null; + if (error instanceof RaceSafeReadError) { + if (error.reason === "too-large") { + throw new Error(`Native lock metadata exceeds ${NATIVE_LOCK_MAX_BYTES} bytes: ${file}`, { + cause: error + }); + } + if (error.reason === "not-regular-file") { + throw new Error(`Native lock must be a regular file: ${file}`, { cause: error }); + } + throw new Error(`Native lock changed while reading: ${file}`, { cause: error }); } - cleanup = { kind, state, manifestHash }; + throw error; } return { - id, - fromArtifactRoot: normalizeWorkflowArtifactRoot(from), - toArtifactRoot: normalizeWorkflowArtifactRoot(to), - stage, - ...cleanup ? { cleanup } : {} + file, + owner: parseNativeLockOwner(JSON.parse(bytes.toString("utf8")), file), + identity: nativeLockFileIdentity(stat) }; } -function normalizeWorkflowNativeProjectConfig(value) { - const native = projectConfigRecord(value, "native"); - if (typeof native.artifact_root !== "string") { - throw new Error("native.artifact_root must be a string"); - } - const clarificationMode = native.clarification_mode ?? "sequential"; - if (clarificationMode !== "sequential" && clarificationMode !== "batch") { - throw new Error("native.clarification_mode must be sequential or batch"); - } - const archiveConfirmation = native.archive_confirmation ?? "automatic"; - if (archiveConfirmation !== "automatic" && archiveConfirmation !== "required") { - throw new Error("native.archive_confirmation must be automatic or required"); - } - const maxVerifyFailures = native.max_verify_failures ?? DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES; - if (!Number.isSafeInteger(maxVerifyFailures) || maxVerifyFailures < 1) { - throw new Error("native.max_verify_failures must be a positive integer"); - } - const pending = normalizeWorkflowPendingRootMove(native.pending_root_move); - return { - artifact_root: normalizeWorkflowArtifactRoot(native.artifact_root), - language: projectConfigLanguage(native.language, "en", "native.language"), - clarification_mode: clarificationMode, - archive_confirmation: archiveConfirmation, - max_verify_failures: maxVerifyFailures, - snapshot: normalizeWorkflowSnapshot(native.snapshot), - ...pending ? { pending_root_move: pending } : {} - }; +async function readNativeLock(file) { + return (await readNativeLockSnapshot(file))?.owner ?? null; } -function normalizeWorkflowClassicProjectConfig(value) { - const classic = projectConfigRecord(value, "classic"); - const contextCompression = classic.context_compression ?? "off"; - if (contextCompression !== "off" && contextCompression !== "beta") { - throw new Error("classic.context_compression must be off or beta"); - } - const reviewMode = classic.review_mode ?? "standard"; - if (reviewMode !== "off" && reviewMode !== "standard" && reviewMode !== "thorough") { - throw new Error("classic.review_mode must be off, standard, or thorough"); - } - const autoTransition = classic.auto_transition ?? true; - if (typeof autoTransition !== "boolean") { - throw new Error("classic.auto_transition must be true or false"); +function diagnosisFromSnapshot(snapshot2) { + if (!snapshot2) return { status: "missing", owner: null, identity: null }; + if (snapshot2.owner.hostname !== os2.hostname()) { + return { status: "unknown", owner: snapshot2.owner, identity: snapshot2.identity }; } + const alive = isProcessAlive(snapshot2.owner.pid); return { - artifact_layout: normalizeClassicArtifactLayout(classic.artifact_layout), - language: projectConfigLanguage(classic.language, "zh-CN", "classic.language"), - context_compression: contextCompression, - review_mode: reviewMode, - auto_transition: autoTransition + status: alive === true ? "active" : alive === false ? "stale" : "unknown", + owner: snapshot2.owner, + identity: snapshot2.identity }; } -function normalizeAmbientResume(value) { - const resolved = value ?? true; - if (typeof resolved !== "boolean") { - throw new Error("ambient_resume must be true or false"); +async function restoreQuarantinedNativeLock(quarantine, file) { + try { + await fs6.lstat(file); + return; + } catch (error) { + if (error.code !== "ENOENT") throw error; } - return resolved; -} -function normalizeWorkflowProjectConfig(root, native, classic, ambientResume, options) { - const hasSchema = root.schema !== void 0; - const hasProjectMarker = hasSchema || root.default_workflow !== void 0 || root.workflows !== void 0 || !options.allowPartialProject && root.native !== void 0; - if (!hasProjectMarker) return null; - if (options.allowPartialProject && !hasSchema) return null; - if (root.schema !== "comet.project.v1") { - throw new Error("Unsupported Comet project schema"); + try { + await fs6.rename(quarantine, file); + } catch (error) { + if (error.code !== "ENOENT") throw error; } - if (root.default_workflow !== "native" && root.default_workflow !== "classic") { - throw new Error("default_workflow must be native or classic"); +} +async function removeBoundNativeLock(expected, quarantineDir) { + const current = await readNativeLockSnapshot(expected.file); + if (!current) return "missing"; + if (current.owner.id !== expected.owner.id) { + throw new Error(`Native lock ownership changed: ${expected.file}`); } - const configuredWorkflows = root.workflows ?? [root.default_workflow]; - if (!Array.isArray(configuredWorkflows) || configuredWorkflows.length === 0 || configuredWorkflows.some((workflow) => workflow !== "native" && workflow !== "classic")) { - throw new Error("workflows must contain native and/or classic"); + if (!sameNativeLockVersion(current.identity, expected.identity)) { + throw new Error(`Native lock identity changed: ${expected.file}`); } - const workflows = [...new Set(configuredWorkflows)]; - if (!workflows.includes(root.default_workflow)) { - throw new Error("workflows must include default_workflow"); + await fs6.mkdir(quarantineDir, { recursive: true }); + const quarantine = path8.join( + quarantineDir, + `${path8.basename(expected.file)}.${expected.owner.id}.${randomUUID2()}.removed` + ); + try { + await fs6.rename(expected.file, quarantine); + } catch (error) { + if (error.code === "ENOENT") return "missing"; + throw error; } - if (workflows.includes("native") && !native) { - throw new Error("native must be a mapping"); + const moved = await readNativeLockSnapshot(quarantine); + if (!moved || moved.owner.id !== expected.owner.id || !sameNativeLockObject(moved.identity, expected.identity)) { + await restoreQuarantinedNativeLock(quarantine, expected.file); + throw new Error(`Native lock changed before quarantine: ${expected.file}`); } + await fs6.rm(quarantine, { force: true }); + return "removed"; +} +function newNativeLockOwner(operation) { return { - schema: "comet.project.v1", - default_workflow: root.default_workflow, - workflows, - ambient_resume: ambientResume, - ...native ? { native } : {}, - ...classic ? { classic } : {} + id: randomUUID2(), + pid: process.pid, + hostname: os2.hostname(), + createdAt: (/* @__PURE__ */ new Date()).toISOString(), + operation }; } -function parseWorkflowProjectConfigDocument(source, options = {}) { - const document = (0, import_yaml.parseDocument)(source, { uniqueKeys: true }); - if (document.errors.length > 0) { - throw new Error(`Invalid .comet/config.yaml: ${document.errors[0].message}`); - } - const parsed = document.toJS(); - if (parsed === null || parsed === void 0) { - return { value: {}, config: null, ambient_resume: true }; +async function writeNativeLockFile(file, owner) { + let handle; + try { + handle = await fs6.open(file, "wx"); + } catch (error) { + if (error.code === "EEXIST") { + const existing = await readNativeLock(file); + throw new Error( + `Native lock is already held: ${file}${existing ? ` by pid ${existing.pid} for ${existing.operation}` : ""}`, + { cause: error } + ); + } + throw error; } - if (typeof parsed !== "object" || Array.isArray(parsed)) { - throw new Error("Invalid .comet/config.yaml: root must be a mapping"); + try { + await handle.writeFile(JSON.stringify(owner, null, 2) + "\n", "utf8"); + await handle.sync(); + return nativeLockFileIdentity(await handle.stat({ bigint: true })); + } finally { + await handle.close(); } - const value = parsed; - const ambientResume = normalizeAmbientResume(value.ambient_resume); - const native = value.native === void 0 ? void 0 : normalizeWorkflowNativeProjectConfig(value.native); - const classic = value.classic === void 0 ? void 0 : normalizeWorkflowClassicProjectConfig(value.classic); - const config = normalizeWorkflowProjectConfig(value, native, classic, ambientResume, { - allowPartialProject: options.allowPartialProject ?? false - }); - return { - value, - config, - ambient_resume: ambientResume, - ...native ? { native } : {}, - ...classic ? { classic } : {} - }; -} -function workflowPendingRootMoveValue(pending) { - return { - id: pending.id, - from_artifact_root: pending.fromArtifactRoot, - to_artifact_root: pending.toArtifactRoot, - stage: pending.stage, - ...pending.cleanup ? { - cleanup: { - kind: pending.cleanup.kind, - state: pending.cleanup.state, - manifest_hash: pending.cleanup.manifestHash - } - } : {} - }; } -function workflowProjectConfigManagedValue(config) { - return { - schema: config.schema, - default_workflow: config.default_workflow, - workflows: config.workflows ?? [config.default_workflow], - ambient_resume: config.ambient_resume, - ...config.native ? { - native: { - artifact_root: config.native.artifact_root, - language: config.native.language, - clarification_mode: config.native.clarification_mode, - archive_confirmation: config.native.archive_confirmation, - max_verify_failures: config.native.max_verify_failures, - snapshot: config.native.snapshot, - ...config.native.pending_root_move ? { - pending_root_move: workflowPendingRootMoveValue(config.native.pending_root_move) - } : {} - } - } : {}, - ...config.classic ? { - classic: { - artifact_layout: config.classic.artifact_layout, - language: config.classic.language, - context_compression: config.classic.context_compression, - review_mode: config.classic.review_mode, - auto_transition: config.classic.auto_transition - } - } : {} - }; -} -function optionalRecord(value) { - return value && typeof value === "object" && !Array.isArray(value) ? value : {}; -} -function mergeWorkflowProjectConfigDocument(existing, config) { - const managed = workflowProjectConfigManagedValue(config); - const validated = parseWorkflowProjectConfigDocument((0, import_yaml.stringify)(managed)).config; - if (!validated) throw new Error("Unsupported Comet project schema"); - const output = { - ...existing, - schema: validated.schema, - default_workflow: validated.default_workflow, - workflows: validated.workflows, - ambient_resume: validated.ambient_resume - }; - if (validated.native) { - const existingNative = optionalRecord(existing.native); - const existingSnapshot = optionalRecord(existingNative.snapshot); - const native = { - ...existingNative, - artifact_root: validated.native.artifact_root, - language: validated.native.language, - clarification_mode: validated.native.clarification_mode, - archive_confirmation: validated.native.archive_confirmation, - max_verify_failures: validated.native.max_verify_failures, - snapshot: { - ...existingSnapshot, - ...validated.native.snapshot - } - }; - if (validated.native.pending_root_move) { - const existingPending = optionalRecord(existingNative.pending_root_move); - const pending = workflowPendingRootMoveValue(validated.native.pending_root_move); - const existingCleanup = optionalRecord(existingPending.cleanup); - const managedCleanup = optionalRecord(pending.cleanup); - native.pending_root_move = { - ...existingPending, - ...pending, - ...pending.cleanup ? { cleanup: { ...existingCleanup, ...managedCleanup } } : {} - }; - } else { - delete native.pending_root_move; +async function publishNativeCoordinatorClaim(paths, operation) { + const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir); + await fs6.mkdir(locksDir, { recursive: true }); + const coordinatorDir = await resolveContainedNativePath( + paths.nativeRoot, + path8.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR) + ); + await fs6.mkdir(coordinatorDir, { recursive: true }); + const owner = newNativeLockOwner(operation); + const temporary = path8.join(coordinatorDir, `.${owner.id}.tmp`); + const file = path8.join(coordinatorDir, `${owner.id}.claim`); + try { + const identity = await writeNativeLockFile(temporary, owner); + await fs6.rename(temporary, file); + const published = await readNativeLockSnapshot(file); + if (!published || !sameNativeLockObject(identity, published.identity)) { + throw new Error(`Native lock coordinator claim changed while publishing: ${file}`); } - output.native = native; - } - if (validated.classic) { - output.classic = { - ...optionalRecord(existing.classic), - artifact_layout: validated.classic.artifact_layout, - language: validated.classic.language, - context_compression: validated.classic.context_compression, - review_mode: validated.classic.review_mode, - auto_transition: validated.classic.auto_transition - }; + return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity: published.identity }; + } finally { + await fs6.rm(temporary, { force: true }); } - return output; } -function defaultWorkflowProjectConfig(artifactRoot = "docs", language = "en") { - return { - schema: "comet.project.v1", - default_workflow: "native", - ambient_resume: true, - native: { - artifact_root: normalizeWorkflowArtifactRoot(artifactRoot), - language, - clarification_mode: "sequential", - archive_confirmation: "automatic", - max_verify_failures: DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES, - snapshot: { - ...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG, - include: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.include], - exclude: [...DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG.exclude] +async function hasNativeCoordinatorPredecessor(claim) { + const coordinatorDir = path8.dirname(claim.file); + let predecessor = false; + const claimName = path8.basename(claim.file); + for (const entry2 of await fs6.readdir(coordinatorDir, { withFileTypes: true })) { + if (!entry2.isFile() || entry2.isSymbolicLink() || !entry2.name.endsWith(".claim")) continue; + const file = path8.join(coordinatorDir, entry2.name); + if (path8.resolve(file) === path8.resolve(claim.file)) continue; + try { + const snapshot2 = await readNativeLockSnapshot(file); + const diagnosis = diagnosisFromSnapshot(snapshot2); + if (diagnosis.status === "missing") continue; + if (diagnosis.status === "stale" && snapshot2) { + await removeBoundNativeLock(snapshot2, coordinatorDir); + continue; } + if (entry2.name < claimName) predecessor = true; + } catch { + if (entry2.name < claimName) predecessor = true; } - }; -} - -// domains/workflow-contract/project-config-reader.ts -import { createHash as createHash5 } from "crypto"; - -// domains/workflow-contract/protected-project-path.ts -import { promises as fs6 } from "fs"; -import path7 from "path"; -function isMissingPath(error) { - const code = error?.code; - return code === "ENOENT" || code === "ENOTDIR"; -} -function isInside4(root, target) { - const relative = path7.relative(root, target); - return relative === "" || !path7.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path7.sep}`); + } + return predecessor; } -async function assertRealProjectRoot(projectRoot, label) { - const lexicalRoot = path7.resolve(projectRoot); - const stat = await fs6.lstat(lexicalRoot); - if (!stat.isDirectory() || stat.isSymbolicLink()) { - throw new Error(`${label} project root must be a real directory`); +async function releaseNativeCoordinatorClaim(claim) { + const current = await readNativeLockSnapshot(claim.file); + if (!current) return; + if (current.owner.id !== claim.owner.id || !sameNativeLockVersion(current.identity, claim.identity)) { + throw new Error(`Native lock coordinator ownership changed: ${claim.file}`); } - return fs6.realpath(lexicalRoot); + await removeBoundNativeLock(current, path8.dirname(claim.file)); } -async function inspectExistingChain(lexicalRoot, realRoot, segments, options) { - let cursor = lexicalRoot; - for (let index = 0; index < segments.length; index++) { - cursor = path7.join(cursor, segments[index]); - let stat; - try { - stat = await fs6.lstat(cursor); - } catch (error) { - if (isMissingPath(error)) return { exists: false, kind: "missing" }; - throw error; - } - const display = path7.relative(lexicalRoot, cursor).replaceAll("\\", "/"); - if (stat.isSymbolicLink()) { - throw new Error(`${options.label} crosses a symbolic link or junction at ${display}`); - } - const final = index === segments.length - 1; - if (!final && !stat.isDirectory()) { - throw new Error(`${options.label} ancestor ${display} must be a real directory`); - } - if (final && (options.expected === "file" && !stat.isFile() || options.expected === "directory" && !stat.isDirectory() || options.expected === "any" && !stat.isFile() && !stat.isDirectory())) { - throw new Error(`${options.label} must be a real ${options.expected}`); - } - const realCursor = await fs6.realpath(cursor); - if (!isInside4(realRoot, realCursor)) { - throw new Error(`${options.label} resolves outside the project root`); - } - if (final) { - return { - exists: true, - kind: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "missing" - }; +async function acquireNativeCoordinator(paths, operation) { + const deadline = Date.now() + NATIVE_LOCK_COORDINATOR_TIMEOUT_MS; + while (true) { + const claim = await publishNativeCoordinatorClaim(paths, operation); + if (!await hasNativeCoordinatorPredecessor(claim)) return claim; + await releaseNativeCoordinatorClaim(claim); + if (Date.now() >= deadline) { + throw new Error(`Native lock coordinator is busy: ${paths.locksDir}`); } + await new Promise((resolve) => setTimeout(resolve, 2 + Math.floor(Math.random() * 7))); } - return { exists: true, kind: "directory" }; } -async function inspectProtectedProjectPath(projectRoot, relativePath, options) { - const relative = normalizeWorkflowRelativePath(relativePath, options.label); - const lexicalRoot = path7.resolve(projectRoot); - const realRoot = await assertRealProjectRoot(lexicalRoot, options.label); - const segments = relative.split("/"); - const target = path7.resolve(lexicalRoot, ...segments); - if (!isInside4(lexicalRoot, target)) { - throw new Error(`${options.label} must stay inside the project root`); - } - const result2 = await inspectExistingChain(lexicalRoot, realRoot, segments, options); - return { - projectRoot: lexicalRoot, - target, - relative, - exists: result2.exists, - kind: result2.kind +async function acquireNativeLocalCoordinator(key) { + const previous = nativeLockLocalCoordinator.get(key) ?? Promise.resolve(); + let release; + const current = new Promise((resolve) => { + release = resolve; + }); + const turn = previous.then(() => current); + nativeLockLocalCoordinator.set(key, turn); + await previous; + return () => { + release(); + if (nativeLockLocalCoordinator.get(key) === turn) nativeLockLocalCoordinator.delete(key); }; } -async function readProtectedProjectFile(projectRoot, relativePath, maxBytes, options) { - const inspection = await inspectProtectedProjectPath(projectRoot, relativePath, { - label: options.label, - expected: "file" - }); - if (!inspection.exists) { - const error = new Error(`${options.label} does not exist`); - error.code = "ENOENT"; - throw error; - } - const realRoot = await assertRealProjectRoot(inspection.projectRoot, options.label); - return readFileRaceSafe(inspection.target, maxBytes, { - ...options, - verify: async (_checkpoint, context) => { - if (!isInside4(realRoot, context.realPath)) { - throw new Error(`${options.label} resolves outside the project root`); +async function withNativeLockCoordinator(paths, operation, work) { + const key = path8.resolve(paths.locksDir); + const current = nativeLockCoordinator.getStore(); + if (current?.has(key)) return work(); + const releaseLocal = await acquireNativeLocalCoordinator(key); + try { + const claim = await acquireNativeCoordinator(paths, operation); + const next = new Map(current ?? []); + next.set(key, claim); + return await nativeLockCoordinator.run(next, async () => { + try { + return await work(); + } finally { + await releaseNativeCoordinatorClaim(claim); } - await inspectExistingChain(inspection.projectRoot, realRoot, inspection.relative.split("/"), { - label: options.label, - expected: "file" - }); - } - }); + }); + } finally { + releaseLocal(); + } } - -// domains/workflow-contract/project-config-reader.ts -var WORKFLOW_PROJECT_CONFIG_PATH = ".comet/config.yaml"; -function workflowProjectConfigIdentityEquals(left, right) { - return left.exists === right.exists && left.sha256 === right.sha256; +async function withNativeLockRecovery(pathEntries, operation, work) { + const unique = [ + ...new Map(pathEntries.map((entry2) => [path8.resolve(entry2.locksDir), entry2])).values() + ].sort((left, right) => path8.resolve(left.locksDir).localeCompare(path8.resolve(right.locksDir))); + const enter = async (index) => { + const entry2 = unique[index]; + return entry2 ? withNativeLockCoordinator(entry2, operation, () => enter(index + 1)) : work(); + }; + return enter(0); } -function isMissingProjectConfig(error) { - const code = error?.code; - return code === "ENOENT" || code === "ENOTDIR"; +async function acquireNativeLock(paths, name, operation) { + return withNativeLockCoordinator(paths, `acquire ${name}`, async () => { + const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir); + await fs6.mkdir(locksDir, { recursive: true }); + const file = await resolveContainedNativePath( + paths.nativeRoot, + path8.join(locksDir, lockName(name)) + ); + const owner = newNativeLockOwner(operation); + const identity = await writeNativeLockFile(file, owner); + return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity }; + }); } -async function readWorkflowProjectConfigDocument(projectRoot, options = {}) { - return (await readWorkflowProjectConfigSnapshot(projectRoot, options)).document; +async function releaseNativeLock(lock) { + if (!await readNativeLockSnapshot(lock.file)) return; + await withNativeLockCoordinator(lock, `release ${path8.basename(lock.file)}`, async () => { + const current = await readNativeLockSnapshot(lock.file); + if (!current) return; + if (current.owner.id !== lock.owner.id) { + throw new Error(`Native lock ownership changed: ${lock.file}`); + } + if (!sameNativeLockVersion(current.identity, lock.identity)) { + throw new Error(`Native lock identity changed: ${lock.file}`); + } + const coordinatorDir = path8.join(lock.locksDir, NATIVE_LOCK_COORDINATOR_DIR); + await removeBoundNativeLock(current, coordinatorDir); + }); } -async function readWorkflowProjectConfigBytes(projectRoot) { +function isProcessAlive(pid) { try { - return (await readProtectedProjectFile( - projectRoot, - WORKFLOW_PROJECT_CONFIG_PATH, - WORKFLOW_PROJECT_CONFIG_MAX_BYTES, - { label: WORKFLOW_PROJECT_CONFIG_PATH } - )).bytes; + process.kill(pid, 0); + return true; } catch (error) { - if (isMissingProjectConfig(error)) return null; - throw error; + const code = error.code; + if (code === "ESRCH") return false; + if (code === "EPERM") return true; + return null; } } -function projectConfigIdentity(bytes) { - return bytes ? { - exists: true, - sha256: createHash5("sha256").update(bytes).digest("hex") - } : { exists: false, sha256: null }; -} -async function readWorkflowProjectConfigIdentity(projectRoot) { - return projectConfigIdentity(await readWorkflowProjectConfigBytes(projectRoot)); +async function diagnoseNativeLock(file) { + return diagnosisFromSnapshot(await readNativeLockSnapshot(file)); } -async function readWorkflowProjectConfigSnapshot(projectRoot, options = {}) { - const bytes = await readWorkflowProjectConfigBytes(projectRoot); - return { - document: bytes ? parseWorkflowProjectConfigDocument(bytes.toString("utf8"), options) : null, - identity: projectConfigIdentity(bytes) - }; +async function takeOverNativeStaleLock(paths, file, expected) { + return withNativeLockCoordinator(paths, `take over ${path8.basename(file)}`, async () => { + const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir); + const containedFile = await resolveContainedNativePath(paths.nativeRoot, file); + if (path8.resolve(path8.dirname(containedFile)) !== path8.resolve(locksDir)) { + throw new Error(`Native lock takeover target is outside the lock directory: ${file}`); + } + const snapshot2 = await readNativeLockSnapshot(containedFile); + const diagnosis = diagnosisFromSnapshot(snapshot2); + if (diagnosis.status === "missing") return { status: "missing" }; + if (expected && !sameNativeLockDiagnosis(expected, diagnosis)) { + return { status: "changed", diagnosis }; + } + if (diagnosis.status !== "stale" || !snapshot2) { + return { status: "changed", diagnosis }; + } + const coordinatorDir = path8.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR); + const removed = await removeBoundNativeLock(snapshot2, coordinatorDir); + return removed === "removed" ? { status: "removed", owner: snapshot2.owner } : { status: "missing" }; + }); } -// domains/workflow-contract/project-config-transaction.ts -var CONFIG_TRANSACTION_MAX_BYTES = 16 * 1024; +// domains/comet-native/native-transaction.ts +import { promises as fs8 } from "fs"; +import path10 from "path"; +import { TextDecoder as TextDecoder3 } from "util"; -// domains/workflow-contract/project-config-writer.ts -async function assertWorkflowProjectConfigIdentity(projectRoot, expectedIdentity) { - if (!expectedIdentity) return; - const current = await readWorkflowProjectConfigIdentity(projectRoot); - if (!workflowProjectConfigIdentityEquals(current, expectedIdentity)) { - throw new Error("Project config changed before commit; rerun the operation"); - } +// domains/comet-native/native-protected-file.ts +import { createHash as createHash4 } from "node:crypto"; +import { constants as fsConstants2, promises as fs7 } from "node:fs"; +import path9 from "node:path"; +import { TextDecoder as TextDecoder2 } from "node:util"; +function isInside4(parent, target) { + const relative = path9.relative(parent, target); + return relative === "" || !path9.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path9.sep}`); } - -// domains/comet-native/native-paths.ts -import { promises as fs7 } from "fs"; -import path8 from "path"; -import os2 from "os"; -var PROJECT_CONFIG_FILE = ".comet/config.yaml"; -async function isFileOrDirectory(target) { - try { - await fs7.lstat(target); - return true; - } catch (error) { - if (error.code === "ENOENT") return false; - throw error; +function positiveLimit2(value) { + if (!Number.isSafeInteger(value) || value < 1) { + throw new Error("Native protected file byte limit must be a positive integer"); } + return value; } -async function declaresNativeProjectConfig(target) { - try { - const source = await fs7.readFile(target, "utf8"); - return /^schema:\s*comet\.project\.v1\s*$/mu.test(source); - } catch (error) { - if (error.code === "ENOENT") return false; - throw error; - } +function sameDirectoryIdentity3(expected, actual) { + return sameFileObject( + { ...expected, birthtime: expected.birthtimeMs }, + { + ...actual, + birthtime: actual.birthtimeMs + } + ); } -function inside(parent, target) { - const relative = path8.relative(parent, target); - return relative === "" || !path8.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path8.sep}`); +function asFileIdentity(stat) { + return { + dev: stat.dev, + ino: stat.ino, + birthtimeMs: stat.birthtimeMs, + ctimeMs: stat.ctimeMs, + mtimeMs: stat.mtimeMs, + size: stat.size + }; } -async function physicalPath(target) { - const missing = []; - let cursor = target; - while (!await isFileOrDirectory(cursor)) { - const parent = path8.dirname(cursor); - if (parent === cursor) break; - missing.push(path8.basename(cursor)); - cursor = parent; - } - const existing = await fs7.realpath(cursor); - return path8.resolve(existing, ...missing.reverse()); +function sameFileIdentity3(expected, actual) { + return sameFileObject( + { ...expected, birthtime: expected.birthtimeMs }, + { + ...actual, + birthtime: actual.birthtimeMs + } + ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size; } -async function isSymbolicLink(target) { - try { - return (await fs7.lstat(target)).isSymbolicLink(); - } catch (error) { - if (error.code === "ENOENT") return false; - throw error; +async function captureDirectoryIdentity2(directory, label) { + const stat = await fs7.lstat(directory); + if (!stat.isDirectory() || stat.isSymbolicLink()) { + throw new Error(`${label} parent must be a real directory: ${directory}`); } + return { + path: directory, + realPath: await fs7.realpath(directory), + dev: stat.dev, + ino: stat.ino, + birthtimeMs: stat.birthtimeMs + }; } -async function discoverNativeProject(startPath) { - let cursor = path8.resolve(startPath); - try { - if (!(await fs7.stat(cursor)).isDirectory()) cursor = path8.dirname(cursor); - } catch (error) { - if (error.code !== "ENOENT") throw error; +async function captureDirectoryChain2(root, directory, label) { + const lexicalRoot = path9.resolve(root); + const lexicalDirectory = path9.resolve(directory); + if (!isInside4(lexicalRoot, lexicalDirectory)) { + throw new Error(`${label} is outside its managed root`); } - const fallback = cursor; - const home = path8.resolve(os2.homedir()); - while (true) { - const isHomeBoundary = cursor === home && fallback !== home; - if (!isHomeBoundary) { - const configFile = path8.join(cursor, ...PROJECT_CONFIG_FILE.split("/")); - const configMarksProject = cursor === fallback || await declaresNativeProjectConfig(configFile); - if (await isFileOrDirectory(configFile) && configMarksProject) { - return cursor; - } + const chain = [await captureDirectoryIdentity2(lexicalRoot, label)]; + let cursor = lexicalRoot; + for (const segment of path9.relative(lexicalRoot, lexicalDirectory).split(path9.sep).filter(Boolean)) { + await verifyDirectoryChain3(chain, label); + cursor = path9.join(cursor, segment); + const identity = await captureDirectoryIdentity2(cursor, label); + if (!isInside4(chain[0].realPath, identity.realPath)) { + throw new Error(`${label} parent resolves outside its managed root: ${cursor}`); } - if (await isFileOrDirectory(path8.join(cursor, ".git"))) return cursor; - const parent = path8.dirname(cursor); - if (parent === cursor) return fallback; - cursor = parent; + chain.push(identity); } + await verifyDirectoryChain3(chain, label); + return chain; } -function normalizeArtifactRootRef(value) { - return normalizeWorkflowArtifactRoot(value); -} -async function resolveArtifactRoot(projectRoot, value) { - const normalized = normalizeArtifactRootRef(value); - const lexical = path8.resolve(projectRoot, ...normalized.split("/")); - const physicalProject = await fs7.realpath(projectRoot); - const physicalTarget = await physicalPath(lexical); - if (!inside(physicalProject, physicalTarget)) { - throw new Error("native.artifact_root resolves outside the project root"); +async function verifyDirectoryChain3(chain, label) { + for (const identity of chain) { + const stat = await fs7.lstat(identity.path); + if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await fs7.realpath(identity.path) !== identity.realPath) { + throw new Error(`${label} parent changed during I/O: ${identity.path}`); + } } - return lexical; } -async function nativeProjectPaths(projectRoot, artifactRootRef) { - const normalized = normalizeArtifactRootRef(artifactRootRef); - const artifactRoot = await resolveArtifactRoot(projectRoot, normalized); - const nativeRoot = path8.join(artifactRoot, "comet"); - if (await isSymbolicLink(nativeRoot)) { - throw new Error("The configured Native comet root must not be a symbolic link"); - } - const [physicalArtifactRoot, physicalNativeRoot] = await Promise.all([ - physicalPath(artifactRoot), - physicalPath(nativeRoot) - ]); - if (!inside(physicalArtifactRoot, physicalNativeRoot)) { - throw new Error("The configured Native comet root resolves outside its artifact root"); +async function readHandleBounded(handle, maxBytes, label) { + const chunks = []; + let total = 0; + const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1)); + while (true) { + const remaining = maxBytes + 1 - total; + const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, remaining), null); + if (bytesRead === 0) break; + total += bytesRead; + if (total > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`); + chunks.push(Buffer.from(buffer.subarray(0, bytesRead))); } - return { - projectRoot: path8.resolve(projectRoot), - configFile: path8.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/")), - artifactRoot, - artifactRootRef: normalized, - nativeRoot, - specsDir: path8.join(nativeRoot, "specs"), - changesDir: path8.join(nativeRoot, "changes"), - archiveDir: path8.join(nativeRoot, "archive"), - runtimeDir: path8.join(nativeRoot, "runtime"), - locksDir: path8.join(nativeRoot, "runtime", "locks"), - transactionsDir: path8.join(nativeRoot, "runtime", "transactions") - }; + return Buffer.concat(chunks, total); } -async function ensureNativeDirectories(paths) { - const directories = [ - paths.specsDir, - paths.changesDir, - paths.archiveDir, - paths.locksDir, - paths.transactionsDir - ]; - await Promise.all( - directories.map(async (directory) => { - await resolveContainedNativePath(paths.nativeRoot, directory); - await fs7.mkdir(directory, { recursive: true }); - }) +async function readNativeProtectedFile(options) { + const maxBytes = positiveLimit2(options.maxBytes); + const file = path9.resolve(options.file); + const chain = await captureDirectoryChain2(options.root, path9.dirname(file), options.label); + const forbidden = await Promise.all( + (options.forbiddenRoots ?? []).map( + (root) => captureDirectoryIdentity2(path9.resolve(root), options.label) + ) ); -} -function isInsidePath(parent, target) { - return inside(path8.resolve(parent), path8.resolve(target)); -} -async function resolveContainedNativePath(root, target) { - const lexicalRoot = path8.resolve(root); - const lexicalTarget = path8.resolve(target); - if (!inside(lexicalRoot, lexicalTarget)) { - throw new Error(`Path is outside the Native root: ${target}`); + await options.hooks?.afterParentChainCaptured?.(); + await verifyDirectoryChain3(chain, options.label); + const before = await fs7.lstat(file); + if (!before.isFile() || before.isSymbolicLink()) { + throw new Error(`${options.label} must be a regular file`); } - if (await isSymbolicLink(lexicalRoot)) { - throw new Error(`Native root must not be a symbolic link: ${root}`); + if (before.size > maxBytes) throw new Error(`${options.label} exceeds ${maxBytes} bytes`); + const beforeIdentity = asFileIdentity(before); + const beforeRealPath = await fs7.realpath(file); + if (!isInside4(chain[0].realPath, beforeRealPath)) { + throw new Error(`${options.label} resolves outside its managed root`); } - const [physicalRoot, physicalTarget] = await Promise.all([ - physicalPath(lexicalRoot), - physicalPath(lexicalTarget) - ]); - if (!inside(physicalRoot, physicalTarget)) { - throw new Error(`Path resolves outside the Native root: ${target}`); + if (forbidden.some((identity) => isInside4(identity.realPath, beforeRealPath))) { + throw new Error(`${options.label} resolves inside an excluded root`); + } + const flags = process.platform === "win32" ? fsConstants2.O_RDONLY : fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW | fsConstants2.O_NONBLOCK; + const handle = await fs7.open(file, flags); + try { + const opened = await handle.stat(); + await options.hooks?.afterOpen?.(); + const [pathAfterOpen, realPathAfterOpen] = await Promise.all([ + fs7.lstat(file), + fs7.realpath(file) + ]); + await verifyDirectoryChain3(chain, options.label); + await verifyDirectoryChain3(forbidden, options.label); + if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity3(beforeIdentity, opened) || !sameFileIdentity3(beforeIdentity, pathAfterOpen)) { + throw new Error(`${options.label} changed while opening`); + } + await options.hooks?.beforeRead?.(); + const bytes = await readHandleBounded(handle, maxBytes, options.label); + await options.hooks?.beforeFinalCheck?.(); + const [afterHandle, afterPath, afterRealPath] = await Promise.all([ + handle.stat(), + fs7.lstat(file), + fs7.realpath(file) + ]); + await verifyDirectoryChain3(chain, options.label); + await verifyDirectoryChain3(forbidden, options.label); + if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameFileIdentity3(beforeIdentity, afterHandle) || !sameFileIdentity3(beforeIdentity, afterPath)) { + throw new Error(`${options.label} changed while reading`); + } + return { + bytes, + hash: createHash4("sha256").update(bytes).digest("hex"), + size: bytes.length + }; + } finally { + await handle.close(); } - return lexicalTarget; -} - -// domains/comet-native/native-config.ts -var DEFAULT_NATIVE_SNAPSHOT_CONFIG = DEFAULT_WORKFLOW_NATIVE_SNAPSHOT_CONFIG; -var DEFAULT_NATIVE_MAX_VERIFY_FAILURES = DEFAULT_WORKFLOW_NATIVE_MAX_VERIFY_FAILURES; -var normalizeNativeSnapshotPattern = normalizeWorkflowSnapshotPattern; -function defaultProjectConfig(artifactRoot = "docs", language = "en") { - return defaultWorkflowProjectConfig(artifactRoot, language); -} -async function readProjectConfig(projectRoot) { - const config = (await readWorkflowProjectConfigDocument(projectRoot))?.config ?? null; - if (!config?.native) return null; - return config; } -async function assertNoPendingNativeRootMove(projectRoot) { - const config = await readProjectConfig(projectRoot); - if (config?.native.pending_root_move) { - throw new Error( - `Native root move ${config.native.pending_root_move.id} is incomplete; use comet native doctor --repair` - ); +async function readNativeProtectedTextFile(options) { + const snapshot2 = await readNativeProtectedFile(options); + let text3; + try { + text3 = new TextDecoder2("utf-8", { fatal: true }).decode(snapshot2.bytes); + } catch (error) { + throw new Error(`${options.label} is not valid UTF-8`, { cause: error }); } + return { ...snapshot2, text: text3 }; } -async function writeProjectConfig(projectRoot, config, options = {}) { - const snapshot2 = await readWorkflowProjectConfigSnapshot(projectRoot, { - allowPartialProject: true - }); - const document = mergeWorkflowProjectConfigDocument(snapshot2.document?.value ?? {}, config); - const canonical = path9.join(projectRoot, ...PROJECT_CONFIG_FILE.split("/")); - await atomicWriteText( - canonical, - renderStructuredProjectConfig(document, config.native.language === "zh-CN" ? "zh-CN" : "en"), - { - containedRoot: projectRoot, - beforeCommit: async () => { - await options.beforeCommit?.(); - await assertWorkflowProjectConfigIdentity(projectRoot, snapshot2.identity); +async function readNativeProtectedDirectory(options) { + const chain = await captureDirectoryChain2(options.root, options.directory, options.label); + let entries; + if (options.maxEntries === void 0) { + entries = await fs7.readdir(options.directory, { withFileTypes: true }); + } else { + const maxEntries = positiveLimit2(options.maxEntries); + entries = []; + const directory = await fs7.opendir(options.directory); + try { + for await (const entry2 of directory) { + entries.push(entry2); + if (entries.length > maxEntries) { + throw new Error(`${options.label} exceeds ${maxEntries} entries`); + } } + } finally { + await directory.close().catch((error) => { + if (error.code !== "ERR_DIR_CLOSED") throw error; + }); } - ); + } + await verifyDirectoryChain3(chain, options.label); + return { + entries, + verify: () => verifyDirectoryChain3(chain, options.label) + }; } -async function resolveNativeProject(options) { - const projectRoot = await discoverNativeProject(options.startPath); - const existing = await readProjectConfig(projectRoot); - if (!existing && options.allowMissingConfig === false) { - throw new Error(`${PROJECT_CONFIG_FILE} was not found`); +async function captureNativeProtectedDirectoryGuard(options) { + const chain = await captureDirectoryChain2(options.root, options.directory, options.label); + return { verify: () => verifyDirectoryChain3(chain, options.label) }; +} +async function quarantineNativeProtectedDirectoryInternal(options) { + const directory = path9.resolve(options.directory); + const quarantine = path9.resolve(options.quarantine); + if (path9.dirname(quarantine) !== path9.dirname(directory) || !isInside4(path9.resolve(options.root), quarantine) || quarantine === directory) { + throw new Error(`${options.label} quarantine must be a distinct sibling inside its root`); } - if (existing?.native.pending_root_move) { - throw new Error( - `Native root move ${existing.native.pending_root_move.id} is incomplete; use comet native doctor --repair` - ); + const parentChain = await captureDirectoryChain2( + options.root, + path9.dirname(directory), + options.label + ); + const identity = await captureDirectoryIdentity2(directory, options.label); + if (!isInside4(parentChain[0].realPath, identity.realPath)) { + throw new Error(`${options.label} resolves outside its managed root`); } - const explicit = options.explicitArtifactRoot ? normalizeArtifactRootRef(options.explicitArtifactRoot) : void 0; - if (existing && explicit && explicit !== existing.native.artifact_root) { - throw new Error( - `Configured Native artifact root is ${existing.native.artifact_root}; refusing conflicting root ${explicit}` - ); + await options.beforeQuarantine?.(); + await verifyDirectoryChain3(parentChain, options.label); + const current = await fs7.lstat(directory); + if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs7.realpath(directory) !== identity.realPath) { + throw new Error(`${options.label} changed before quarantine`); } - const config = existing ?? defaultProjectConfig(explicit ?? "docs"); - const paths = await nativeProjectPaths(projectRoot, config.native.artifact_root); - return { config, paths, configured: existing !== null }; -} - -// domains/comet-native/native-mutation-lock.ts -import { promises as fs11 } from "fs"; -import path13 from "path"; - -// domains/comet-native/native-lock.ts -import { AsyncLocalStorage } from "async_hooks"; -import { randomUUID as randomUUID2 } from "crypto"; -import { promises as fs8 } from "fs"; -import os3 from "os"; -import path10 from "path"; -var NATIVE_LOCK_MAX_BYTES = 16 * 1024; -var NATIVE_LOCK_COORDINATOR_DIR = ".coordinator"; -var NATIVE_LOCK_COORDINATOR_TIMEOUT_MS = 5e3; -var nativeLockCoordinator = new AsyncLocalStorage(); -var nativeLockLocalCoordinator = /* @__PURE__ */ new Map(); -function lockName(value) { - if (!/^[a-z][a-z0-9-]*$/u.test(value)) throw new Error(`Invalid Native lock name: ${value}`); - return `${value}.lock`; -} -function parseNativeLockOwner(value, file) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`Invalid Native lock metadata: ${file}`); + try { + await fs7.lstat(quarantine); + throw new Error(`${options.label} quarantine path is occupied`); + } catch (error) { + if (error.code !== "ENOENT") throw error; } - const owner = value; - if (typeof owner.id !== "string" || owner.id.length === 0 || typeof owner.pid !== "number" || !Number.isSafeInteger(owner.pid) || owner.pid < 1 || typeof owner.hostname !== "string" || owner.hostname.length === 0 || typeof owner.createdAt !== "string" || owner.createdAt.length === 0 || typeof owner.operation !== "string" || owner.operation.length === 0) { - throw new Error(`Invalid Native lock metadata: ${file}`); + await fs7.rename(directory, quarantine); + await verifyDirectoryChain3(parentChain, options.label); + const quarantined = await fs7.lstat(quarantine); + if (!quarantined.isDirectory() || quarantined.isSymbolicLink() || !sameDirectoryIdentity3(identity, quarantined)) { + throw new Error(`${options.label} changed while quarantining`); } - return owner; -} -function nativeLockFileIdentity(stat) { - return { - device: stat.dev.toString(), - inode: stat.ino.toString(), - size: stat.size.toString(), - birthtimeNs: stat.birthtimeNs.toString(), - ctimeNs: stat.ctimeNs.toString(), - mtimeNs: stat.mtimeNs.toString() - }; -} -function sameNativeLockObject(left, right) { - const leftObject = { dev: left.device, ino: left.inode, birthtime: left.birthtimeNs }; - const rightObject = { dev: right.device, ino: right.inode, birthtime: right.birthtimeNs }; - if (hasComparableFileObject(leftObject, rightObject)) { - return sameFileObject(leftObject, rightObject); + await options.afterQuarantine?.(quarantine); + try { + await fs7.lstat(directory); + throw new Error(`${options.label} path was recreated while quarantining`); + } catch (error) { + if (error.code !== "ENOENT") throw error; } - return sameFileObject(leftObject, rightObject) && left.size === right.size; + return { quarantine, parentChain, identity }; } -function sameNativeLockVersion(left, right) { - return sameNativeLockObject(left, right) && left.size === right.size && left.birthtimeNs === right.birthtimeNs && left.ctimeNs === right.ctimeNs && left.mtimeNs === right.mtimeNs; +async function quarantineNativeProtectedDirectory(options) { + await quarantineNativeProtectedDirectoryInternal(options); } -function sameNativeLockDiagnosis(left, right) { - if (left.status !== right.status) return false; - if (!left.owner || !left.identity || !right.owner || !right.identity) { - return left.owner === right.owner && left.identity === right.identity; +async function removeNativeProtectedFile(options) { + const file = path9.resolve(options.file); + const parentChain = await captureDirectoryChain2(options.root, path9.dirname(file), options.label); + const before = await fs7.lstat(file); + if (!before.isFile() || before.isSymbolicLink()) { + throw new Error(`${options.label} must be a regular file`); } - return left.owner.id === right.owner.id && sameNativeLockVersion(left.identity, right.identity); -} -async function readNativeLockSnapshot(file) { - let bytes; - let stat; - try { - const result2 = await readFileRaceSafe(file, NATIVE_LOCK_MAX_BYTES, { - bigint: true, - label: "Native lock" - }); - bytes = result2.bytes; - stat = result2.stat; - } catch (error) { - if (error.code === "ENOENT") return null; - if (error instanceof RaceSafeReadError) { - if (error.reason === "too-large") { - throw new Error(`Native lock metadata exceeds ${NATIVE_LOCK_MAX_BYTES} bytes: ${file}`, { - cause: error - }); - } - if (error.reason === "not-regular-file") { - throw new Error(`Native lock must be a regular file: ${file}`, { cause: error }); - } - throw new Error(`Native lock changed while reading: ${file}`, { cause: error }); - } - throw error; + const identity = asFileIdentity(before); + const realPath = await fs7.realpath(file); + if (!isInside4(parentChain[0].realPath, realPath)) { + throw new Error(`${options.label} resolves outside its managed root`); } - return { + const snapshot2 = await readNativeProtectedFile({ + root: options.root, file, - owner: parseNativeLockOwner(JSON.parse(bytes.toString("utf8")), file), - identity: nativeLockFileIdentity(stat) - }; -} -async function readNativeLock(file) { - return (await readNativeLockSnapshot(file))?.owner ?? null; + maxBytes: options.maxBytes, + label: options.label + }); + if (snapshot2.hash !== options.expectedHash || snapshot2.size !== options.expectedSize) { + throw new Error(`${options.label} changed before removal`); + } + const [afterRead, afterReadRealPath] = await Promise.all([fs7.lstat(file), fs7.realpath(file)]); + if (!afterRead.isFile() || afterRead.isSymbolicLink() || !sameFileIdentity3(identity, afterRead) || afterReadRealPath !== realPath) { + throw new Error(`${options.label} changed while verifying removal`); + } + await options.beforeRemove?.(); + await verifyDirectoryChain3(parentChain, options.label); + const [current, currentRealPath] = await Promise.all([fs7.lstat(file), fs7.realpath(file)]); + if (!current.isFile() || current.isSymbolicLink() || !sameFileIdentity3(identity, current) || currentRealPath !== realPath) { + throw new Error(`${options.label} changed before removal`); + } + await fs7.rm(file); + await verifyDirectoryChain3(parentChain, options.label); } -function diagnosisFromSnapshot(snapshot2) { - if (!snapshot2) return { status: "missing", owner: null, identity: null }; - if (snapshot2.owner.hostname !== os3.hostname()) { - return { status: "unknown", owner: snapshot2.owner, identity: snapshot2.identity }; +async function removeNativeProtectedEmptyDirectory(options) { + const directory = path9.resolve(options.directory); + const parentChain = await captureDirectoryChain2( + options.root, + path9.dirname(directory), + options.label + ); + const identity = await captureDirectoryIdentity2(directory, options.label); + if (!isInside4(parentChain[0].realPath, identity.realPath)) { + throw new Error(`${options.label} resolves outside its managed root`); } - const alive = isProcessAlive(snapshot2.owner.pid); - return { - status: alive === true ? "active" : alive === false ? "stale" : "unknown", - owner: snapshot2.owner, - identity: snapshot2.identity - }; + await options.beforeRemove?.(); + await verifyDirectoryChain3(parentChain, options.label); + const current = await fs7.lstat(directory); + if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs7.realpath(directory) !== identity.realPath) { + throw new Error(`${options.label} changed before removal`); + } + await fs7.rmdir(directory); + await verifyDirectoryChain3(parentChain, options.label); } -async function restoreQuarantinedNativeLock(quarantine, file) { - try { - await fs8.lstat(file); - return; - } catch (error) { - if (error.code !== "ENOENT") throw error; +async function ensureNativeProtectedDirectory(options) { + const lexicalRoot = path9.resolve(options.root); + const lexicalDirectory = path9.resolve(options.directory); + if (!isInside4(lexicalRoot, lexicalDirectory)) { + throw new Error(`${options.label} is outside its managed root`); } - try { - await fs8.rename(quarantine, file); - } catch (error) { - if (error.code !== "ENOENT") throw error; + const chain = [await captureDirectoryIdentity2(lexicalRoot, options.label)]; + let cursor = lexicalRoot; + for (const segment of path9.relative(lexicalRoot, lexicalDirectory).split(path9.sep).filter(Boolean)) { + await verifyDirectoryChain3(chain, options.label); + cursor = path9.join(cursor, segment); + try { + await fs7.mkdir(cursor); + } catch (error) { + if (error.code !== "EEXIST") throw error; + } + const identity = await captureDirectoryIdentity2(cursor, options.label); + if (!isInside4(chain[0].realPath, identity.realPath)) { + throw new Error(`${options.label} resolves outside its managed root: ${cursor}`); + } + chain.push(identity); } + await verifyDirectoryChain3(chain, options.label); } -async function removeBoundNativeLock(expected, quarantineDir) { - const current = await readNativeLockSnapshot(expected.file); - if (!current) return "missing"; - if (current.owner.id !== expected.owner.id) { - throw new Error(`Native lock ownership changed: ${expected.file}`); +async function moveNativeProtectedDirectory(options) { + const root = path9.resolve(options.root); + const source = path9.resolve(options.source); + const target = path9.resolve(options.target); + if (source === target || !isInside4(root, source) || !isInside4(root, target) || isInside4(source, target) || isInside4(target, source)) { + throw new Error(`${options.label} source and target must be distinct paths inside one root`); } - if (!sameNativeLockVersion(current.identity, expected.identity)) { - throw new Error(`Native lock identity changed: ${expected.file}`); + await ensureNativeProtectedDirectory({ + root, + directory: path9.dirname(target), + label: `${options.label} target parent` + }); + const sourceParentChain = await captureDirectoryChain2(root, path9.dirname(source), options.label); + const targetParentChain = await captureDirectoryChain2(root, path9.dirname(target), options.label); + const sourceIdentity = await captureDirectoryIdentity2(source, options.label); + if (!isInside4(sourceParentChain[0].realPath, sourceIdentity.realPath)) { + throw new Error(`${options.label} source resolves outside its managed root`); } - await fs8.mkdir(quarantineDir, { recursive: true }); - const quarantine = path10.join( - quarantineDir, - `${path10.basename(expected.file)}.${expected.owner.id}.${randomUUID2()}.removed` - ); try { - await fs8.rename(expected.file, quarantine); + await fs7.lstat(target); + throw new Error(`${options.label} target already exists`); } catch (error) { - if (error.code === "ENOENT") return "missing"; - throw error; + if (error.code !== "ENOENT") throw error; } - const moved = await readNativeLockSnapshot(quarantine); - if (!moved || moved.owner.id !== expected.owner.id || !sameNativeLockObject(moved.identity, expected.identity)) { - await restoreQuarantinedNativeLock(quarantine, expected.file); - throw new Error(`Native lock changed before quarantine: ${expected.file}`); + await options.beforeMove?.(); + await Promise.all([ + verifyDirectoryChain3(sourceParentChain, options.label), + verifyDirectoryChain3(targetParentChain, options.label) + ]); + const current = await fs7.lstat(source); + if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, current) || await fs7.realpath(source) !== sourceIdentity.realPath) { + throw new Error(`${options.label} source changed before move`); } - await fs8.rm(quarantine, { force: true }); - return "removed"; -} -function newNativeLockOwner(operation) { - return { - id: randomUUID2(), - pid: process.pid, - hostname: os3.hostname(), - createdAt: (/* @__PURE__ */ new Date()).toISOString(), - operation - }; -} -async function writeNativeLockFile(file, owner) { - let handle; - try { - handle = await fs8.open(file, "wx"); - } catch (error) { - if (error.code === "EEXIST") { - const existing = await readNativeLock(file); - throw new Error( - `Native lock is already held: ${file}${existing ? ` by pid ${existing.pid} for ${existing.operation}` : ""}`, - { cause: error } - ); - } - throw error; + await fs7.rename(source, target); + await Promise.all([ + verifyDirectoryChain3(sourceParentChain, options.label), + verifyDirectoryChain3(targetParentChain, options.label) + ]); + const moved = await fs7.lstat(target); + if (!moved.isDirectory() || moved.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, moved) || !isInside4(sourceParentChain[0].realPath, await fs7.realpath(target))) { + throw new Error(`${options.label} source identity changed while moving`); } try { - await handle.writeFile(JSON.stringify(owner, null, 2) + "\n", "utf8"); - await handle.sync(); - return nativeLockFileIdentity(await handle.stat({ bigint: true })); - } finally { - await handle.close(); + await fs7.lstat(source); + throw new Error(`${options.label} source was recreated while moving`); + } catch (error) { + if (error.code !== "ENOENT") throw error; } } -async function publishNativeCoordinatorClaim(paths, operation) { - const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir); - await fs8.mkdir(locksDir, { recursive: true }); - const coordinatorDir = await resolveContainedNativePath( - paths.nativeRoot, - path10.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR) - ); - await fs8.mkdir(coordinatorDir, { recursive: true }); - const owner = newNativeLockOwner(operation); - const temporary = path10.join(coordinatorDir, `.${owner.id}.tmp`); - const file = path10.join(coordinatorDir, `${owner.id}.claim`); - try { - const identity = await writeNativeLockFile(temporary, owner); - await fs8.rename(temporary, file); - const published = await readNativeLockSnapshot(file); - if (!published || !sameNativeLockObject(identity, published.identity)) { - throw new Error(`Native lock coordinator claim changed while publishing: ${file}`); - } - return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity: published.identity }; - } finally { - await fs8.rm(temporary, { force: true }); +async function copyNativeProtectedFile(options) { + const source = await readNativeProtectedFile({ + root: options.sourceRoot, + file: options.source, + maxBytes: options.maxBytes, + label: options.label, + hooks: options.hooks, + forbiddenRoots: options.forbiddenRoots + }); + if (options.expectedHash !== void 0 && source.hash !== options.expectedHash) { + throw new Error(`${options.label} content changed before copy`); } -} -async function hasNativeCoordinatorPredecessor(claim) { - const coordinatorDir = path10.dirname(claim.file); - let predecessor = false; - const claimName = path10.basename(claim.file); - for (const entry2 of await fs8.readdir(coordinatorDir, { withFileTypes: true })) { - if (!entry2.isFile() || entry2.isSymbolicLink() || !entry2.name.endsWith(".claim")) continue; - const file = path10.join(coordinatorDir, entry2.name); - if (path10.resolve(file) === path10.resolve(claim.file)) continue; - try { - const snapshot2 = await readNativeLockSnapshot(file); - const diagnosis = diagnosisFromSnapshot(snapshot2); - if (diagnosis.status === "missing") continue; - if (diagnosis.status === "stale" && snapshot2) { - await removeBoundNativeLock(snapshot2, coordinatorDir); - continue; + await atomicWriteBytes(options.target, source.bytes, { + containedRoot: options.targetRoot, + exclusive: options.exclusive, + beforeCommit: async () => { + await options.hooks?.beforeTargetCommit?.(); + if (options.expectedTargetHash === void 0) return; + if (options.expectedTargetHash === null) { + try { + await fs7.lstat(options.target); + } catch (error) { + if (error.code === "ENOENT") return; + throw error; + } + throw new Error(`${options.label} target changed before commit`); + } + const target = await readNativeProtectedFile({ + root: options.targetRoot, + file: options.target, + maxBytes: options.maxBytes, + label: `${options.label} existing target` + }); + if (target.hash !== options.expectedTargetHash) { + throw new Error(`${options.label} target changed before commit`); } - if (entry2.name < claimName) predecessor = true; - } catch { - if (entry2.name < claimName) predecessor = true; } + }); + const persisted = await readNativeProtectedFile({ + root: options.targetRoot, + file: options.target, + maxBytes: options.maxBytes, + label: `${options.label} target` + }); + if (persisted.hash !== source.hash || persisted.size !== source.size) { + throw new Error(`${options.label} target could not be verified`); } - return predecessor; -} -async function releaseNativeCoordinatorClaim(claim) { - const current = await readNativeLockSnapshot(claim.file); - if (!current) return; - if (current.owner.id !== claim.owner.id || !sameNativeLockVersion(current.identity, claim.identity)) { - throw new Error(`Native lock coordinator ownership changed: ${claim.file}`); - } - await removeBoundNativeLock(current, path10.dirname(claim.file)); + return persisted; } -async function acquireNativeCoordinator(paths, operation) { - const deadline = Date.now() + NATIVE_LOCK_COORDINATOR_TIMEOUT_MS; - while (true) { - const claim = await publishNativeCoordinatorClaim(paths, operation); - if (!await hasNativeCoordinatorPredecessor(claim)) return claim; - await releaseNativeCoordinatorClaim(claim); - if (Date.now() >= deadline) { - throw new Error(`Native lock coordinator is busy: ${paths.locksDir}`); - } - await new Promise((resolve) => setTimeout(resolve, 2 + Math.floor(Math.random() * 7))); - } -} -async function acquireNativeLocalCoordinator(key) { - const previous = nativeLockLocalCoordinator.get(key) ?? Promise.resolve(); - let release; - const current = new Promise((resolve) => { - release = resolve; - }); - const turn = previous.then(() => current); - nativeLockLocalCoordinator.set(key, turn); - await previous; - return () => { - release(); - if (nativeLockLocalCoordinator.get(key) === turn) nativeLockLocalCoordinator.delete(key); - }; -} -async function withNativeLockCoordinator(paths, operation, work) { - const key = path10.resolve(paths.locksDir); - const current = nativeLockCoordinator.getStore(); - if (current?.has(key)) return work(); - const releaseLocal = await acquireNativeLocalCoordinator(key); - try { - const claim = await acquireNativeCoordinator(paths, operation); - const next = new Map(current ?? []); - next.set(key, claim); - return await nativeLockCoordinator.run(next, async () => { - try { - return await work(); - } finally { - await releaseNativeCoordinatorClaim(claim); - } - }); - } finally { - releaseLocal(); + +// domains/comet-native/native-transaction.ts +var JOURNAL_KEYS = /* @__PURE__ */ new Set([ + "schema", + "id", + "kind", + "status", + "projectRoot", + "nativeRoot", + "change", + "createdAt", + "operations" +]); +var OPERATION_KEYS = /* @__PURE__ */ new Set(["id", "type", "source", "target", "staged", "backup"]); +var ARCHIVE_V2_JOURNAL_KEYS = /* @__PURE__ */ new Set([ + "schema", + "id", + "kind", + "status", + "change", + "createdAt", + "preflightHash", + "operations" +]); +var ARCHIVE_V2_OPERATION_KEYS = /* @__PURE__ */ new Set([ + "id", + "type", + "source", + "target", + "staged", + "backup", + "expectedSourceHash", + "expectedTargetHash", + "stagedHash" +]); +var EVENT_KEYS = /* @__PURE__ */ new Set(["sequence", "timestamp", "type", "operationId"]); +var TRANSACTION_STATUSES = /* @__PURE__ */ new Set([ + "prepared", + "applying", + "committed", + "rolling-back", + "rolled-back" +]); +var EVENT_TYPES = /* @__PURE__ */ new Set([ + "prepared", + "operation-started", + "operation-completed", + "archive-finalization-started", + "archive-finalized", + "commit", + "rollback-started", + "rollback-completed" +]); +var NATIVE_TRANSACTION_JOURNAL_MAX_BYTES = 256 * 1024; +var NATIVE_TRANSACTION_EVENTS_MAX_BYTES = 1024 * 1024; +var NATIVE_TRANSACTION_EVENT_MAX_BYTES = 16 * 1024; +var NATIVE_TRANSACTION_EVENT_MAX_COUNT = 1024; +var NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES = 64 * 1024 * 1024; +var NATIVE_LEGACY_TRANSACTION_DIRECTORY_MAX_ENTRIES = 2e4; +var UTF8_DECODER = new TextDecoder3("utf-8", { fatal: true }); +function record(value, label) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`${label} must be an object`); } + return value; } -async function withNativeLockRecovery(pathEntries, operation, work) { - const unique = [ - ...new Map(pathEntries.map((entry2) => [path10.resolve(entry2.locksDir), entry2])).values() - ].sort((left, right) => path10.resolve(left.locksDir).localeCompare(path10.resolve(right.locksDir))); - const enter = async (index) => { - const entry2 = unique[index]; - return entry2 ? withNativeLockCoordinator(entry2, operation, () => enter(index + 1)) : work(); - }; - return enter(0); +function rejectUnknown(value, keys, label) { + const unknown = Object.keys(value).filter((key) => !keys.has(key)); + if (unknown.length > 0) throw new Error(`${label} has unknown field(s): ${unknown.join(", ")}`); } -async function acquireNativeLock(paths, name, operation) { - return withNativeLockCoordinator(paths, `acquire ${name}`, async () => { - const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir); - await fs8.mkdir(locksDir, { recursive: true }); - const file = await resolveContainedNativePath( - paths.nativeRoot, - path10.join(locksDir, lockName(name)) - ); - const owner = newNativeLockOwner(operation); - const identity = await writeNativeLockFile(file, owner); - return { file, nativeRoot: paths.nativeRoot, locksDir, owner, identity }; - }); +function validTimestamp(value) { + if (typeof value !== "string") return false; + const parsed = new Date(value); + return !Number.isNaN(parsed.valueOf()) && parsed.toISOString() === value; } -async function releaseNativeLock(lock) { - if (!await readNativeLockSnapshot(lock.file)) return; - await withNativeLockCoordinator(lock, `release ${path10.basename(lock.file)}`, async () => { - const current = await readNativeLockSnapshot(lock.file); - if (!current) return; - if (current.owner.id !== lock.owner.id) { - throw new Error(`Native lock ownership changed: ${lock.file}`); - } - if (!sameNativeLockVersion(current.identity, lock.identity)) { - throw new Error(`Native lock identity changed: ${lock.file}`); - } - const coordinatorDir = path10.join(lock.locksDir, NATIVE_LOCK_COORDINATOR_DIR); - await removeBoundNativeLock(current, coordinatorDir); - }); +function assertRef(ref, label) { + if (typeof ref !== "string" || ref.length === 0 || path10.isAbsolute(ref) || /^(?:[A-Za-z]:|~|[\\/])/u.test(ref) || ref.split(/[\\/]/u).includes("..")) { + throw new Error(`${label} must stay inside the Native root`); + } } -function isProcessAlive(pid) { - try { - process.kill(pid, 0); - return true; - } catch (error) { - const code = error.code; - if (code === "ESRCH") return false; - if (code === "EPERM") return true; - return null; +function assertHash(value, label) { + if (typeof value !== "string" || !/^[a-f0-9]{64}$/u.test(value)) { + throw new Error(`${label} must be a SHA-256 hash`); } } -async function diagnoseNativeLock(file) { - return diagnosisFromSnapshot(await readNativeLockSnapshot(file)); +function assertArchiveRef(ref, label) { + assertRef(ref, label); + if (ref.includes("\\") || ref !== path10.posix.normalize(ref) || ref.split("/").includes(".") || ref.endsWith("/") || Buffer.byteLength(ref, "utf8") > 1024) { + throw new Error(`${label} must be a normalized Native-relative ref`); + } } -async function takeOverNativeStaleLock(paths, file, expected) { - return withNativeLockCoordinator(paths, `take over ${path10.basename(file)}`, async () => { - const locksDir = await resolveContainedNativePath(paths.nativeRoot, paths.locksDir); - const containedFile = await resolveContainedNativePath(paths.nativeRoot, file); - if (path10.resolve(path10.dirname(containedFile)) !== path10.resolve(locksDir)) { - throw new Error(`Native lock takeover target is outside the lock directory: ${file}`); - } - const snapshot2 = await readNativeLockSnapshot(containedFile); - const diagnosis = diagnosisFromSnapshot(snapshot2); - if (diagnosis.status === "missing") return { status: "missing" }; - if (expected && !sameNativeLockDiagnosis(expected, diagnosis)) { - return { status: "changed", diagnosis }; - } - if (diagnosis.status !== "stale" || !snapshot2) { - return { status: "changed", diagnosis }; +function parseOperation(value, index) { + const operation = record(value, `transaction operations[${index}]`); + rejectUnknown(operation, OPERATION_KEYS, `transaction operations[${index}]`); + if (typeof operation.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(operation.id)) { + throw new Error(`transaction operations[${index}].id is invalid`); + } + if (operation.type !== "write" && operation.type !== "remove" && operation.type !== "move") { + throw new Error(`transaction operation ${operation.id} has an invalid type`); + } + assertRef(operation.target, `transaction operation ${operation.id} target`); + for (const field2 of ["source", "staged", "backup"]) { + if (operation[field2] !== void 0) { + assertRef(operation[field2], `transaction operation ${operation.id} ${field2}`); } - const coordinatorDir = path10.join(locksDir, NATIVE_LOCK_COORDINATOR_DIR); - const removed = await removeBoundNativeLock(snapshot2, coordinatorDir); - return removed === "removed" ? { status: "removed", owner: snapshot2.owner } : { status: "missing" }; - }); -} - -// domains/comet-native/native-transaction.ts -import { promises as fs10 } from "fs"; -import path12 from "path"; -import { TextDecoder as TextDecoder3 } from "util"; - -// domains/comet-native/native-protected-file.ts -import { createHash as createHash6 } from "node:crypto"; -import { constants as fsConstants3, promises as fs9 } from "node:fs"; -import path11 from "node:path"; -import { TextDecoder as TextDecoder2 } from "node:util"; -function isInside5(parent, target) { - const relative = path11.relative(parent, target); - return relative === "" || !path11.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path11.sep}`); -} -function positiveLimit2(value) { - if (!Number.isSafeInteger(value) || value < 1) { - throw new Error("Native protected file byte limit must be a positive integer"); } - return value; -} -function sameDirectoryIdentity3(expected, actual) { - return sameFileObject( - { ...expected, birthtime: expected.birthtimeMs }, - { - ...actual, - birthtime: actual.birthtimeMs + if (operation.type === "write") { + if (operation.staged === void 0 || operation.source !== void 0) { + throw new Error(`write operation ${operation.id} requires staged and forbids source`); } - ); -} -function asFileIdentity(stat) { - return { - dev: stat.dev, - ino: stat.ino, - birthtimeMs: stat.birthtimeMs, - ctimeMs: stat.ctimeMs, - mtimeMs: stat.mtimeMs, - size: stat.size - }; -} -function sameFileIdentity3(expected, actual) { - return sameFileObject( - { ...expected, birthtime: expected.birthtimeMs }, - { - ...actual, - birthtime: actual.birthtimeMs + } else if (operation.type === "remove") { + if (operation.source !== void 0 || operation.staged !== void 0) { + throw new Error(`remove operation ${operation.id} forbids source and staged`); } - ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size; -} -async function captureDirectoryIdentity2(directory, label) { - const stat = await fs9.lstat(directory); - if (!stat.isDirectory() || stat.isSymbolicLink()) { - throw new Error(`${label} parent must be a real directory: ${directory}`); + } else if (operation.source === void 0 || operation.staged !== void 0 || operation.backup !== void 0) { + throw new Error(`move operation ${operation.id} requires source and forbids staged and backup`); } - return { - path: directory, - realPath: await fs9.realpath(directory), - dev: stat.dev, - ino: stat.ino, - birthtimeMs: stat.birthtimeMs - }; + return operation; } -async function captureDirectoryChain2(root, directory, label) { - const lexicalRoot = path11.resolve(root); - const lexicalDirectory = path11.resolve(directory); - if (!isInside5(lexicalRoot, lexicalDirectory)) { - throw new Error(`${label} is outside its managed root`); +function parseNativeArchiveTransactionJournalV2(value) { + const journal = record(value, "Native Archive transaction journal"); + rejectUnknown(journal, ARCHIVE_V2_JOURNAL_KEYS, "Native Archive transaction journal"); + if (journal.schema !== "comet.native.transaction.v2") { + throw new Error("Unsupported Native Archive transaction schema"); } - const chain = [await captureDirectoryIdentity2(lexicalRoot, label)]; - let cursor = lexicalRoot; - for (const segment of path11.relative(lexicalRoot, lexicalDirectory).split(path11.sep).filter(Boolean)) { - await verifyDirectoryChain3(chain, label); - cursor = path11.join(cursor, segment); - const identity = await captureDirectoryIdentity2(cursor, label); - if (!isInside5(chain[0].realPath, identity.realPath)) { - throw new Error(`${label} parent resolves outside its managed root: ${cursor}`); - } - chain.push(identity); - } - await verifyDirectoryChain3(chain, label); - return chain; -} -async function verifyDirectoryChain3(chain, label) { - for (const identity of chain) { - const stat = await fs9.lstat(identity.path); - if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await fs9.realpath(identity.path) !== identity.realPath) { - throw new Error(`${label} parent changed during I/O: ${identity.path}`); - } + if (typeof journal.id !== "string" || !/^[a-f0-9-]{8,}$/u.test(journal.id)) { + throw new Error("Native Archive transaction id is invalid"); } -} -async function readHandleBounded(handle, maxBytes, label) { - const chunks = []; - let total = 0; - const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1)); - while (true) { - const remaining = maxBytes + 1 - total; - const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, remaining), null); - if (bytesRead === 0) break; - total += bytesRead; - if (total > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`); - chunks.push(Buffer.from(buffer.subarray(0, bytesRead))); + if (journal.kind !== "archive") throw new Error("Native v2 transaction kind must be archive"); + if (typeof journal.status !== "string" || !TRANSACTION_STATUSES.has(journal.status)) { + throw new Error("Native Archive transaction status is invalid"); } - return Buffer.concat(chunks, total); -} -async function readNativeProtectedFile(options) { - const maxBytes = positiveLimit2(options.maxBytes); - const file = path11.resolve(options.file); - const chain = await captureDirectoryChain2(options.root, path11.dirname(file), options.label); - const forbidden = await Promise.all( - (options.forbiddenRoots ?? []).map( - (root) => captureDirectoryIdentity2(path11.resolve(root), options.label) - ) - ); - await options.hooks?.afterParentChainCaptured?.(); - await verifyDirectoryChain3(chain, options.label); - const before = await fs9.lstat(file); - if (!before.isFile() || before.isSymbolicLink()) { - throw new Error(`${options.label} must be a regular file`); + if (typeof journal.change !== "string" || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(journal.change)) { + throw new Error("Native Archive transaction change name is invalid"); } - if (before.size > maxBytes) throw new Error(`${options.label} exceeds ${maxBytes} bytes`); - const beforeIdentity = asFileIdentity(before); - const beforeRealPath = await fs9.realpath(file); - if (!isInside5(chain[0].realPath, beforeRealPath)) { - throw new Error(`${options.label} resolves outside its managed root`); + if (!validTimestamp(journal.createdAt)) { + throw new Error("Native Archive transaction createdAt is invalid"); } - if (forbidden.some((identity) => isInside5(identity.realPath, beforeRealPath))) { - throw new Error(`${options.label} resolves inside an excluded root`); + assertHash(journal.preflightHash, "Native Archive transaction preflightHash"); + if (!Array.isArray(journal.operations) || journal.operations.length > 65) { + throw new Error("Native Archive transaction operations must be an array"); } - const flags = process.platform === "win32" ? fsConstants3.O_RDONLY : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK; - const handle = await fs9.open(file, flags); - try { - const opened = await handle.stat(); - await options.hooks?.afterOpen?.(); - const [pathAfterOpen, realPathAfterOpen] = await Promise.all([ - fs9.lstat(file), - fs9.realpath(file) - ]); - await verifyDirectoryChain3(chain, options.label); - await verifyDirectoryChain3(forbidden, options.label); - if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity3(beforeIdentity, opened) || !sameFileIdentity3(beforeIdentity, pathAfterOpen)) { - throw new Error(`${options.label} changed while opening`); + const operations = journal.operations.map((value2, index) => { + const operation = record(value2, `Archive transaction operations[${index}]`); + rejectUnknown(operation, ARCHIVE_V2_OPERATION_KEYS, `Archive transaction operations[${index}]`); + if (typeof operation.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(operation.id)) { + throw new Error(`Archive transaction operations[${index}].id is invalid`); } - await options.hooks?.beforeRead?.(); - const bytes = await readHandleBounded(handle, maxBytes, options.label); - await options.hooks?.beforeFinalCheck?.(); - const [afterHandle, afterPath, afterRealPath] = await Promise.all([ - handle.stat(), - fs9.lstat(file), - fs9.realpath(file) - ]); - await verifyDirectoryChain3(chain, options.label); - await verifyDirectoryChain3(forbidden, options.label); - if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameFileIdentity3(beforeIdentity, afterHandle) || !sameFileIdentity3(beforeIdentity, afterPath)) { - throw new Error(`${options.label} changed while reading`); + if (operation.type !== "write" && operation.type !== "remove" && operation.type !== "move") { + throw new Error(`Archive transaction operation ${operation.id} has an invalid type`); } - return { - bytes, - hash: createHash6("sha256").update(bytes).digest("hex"), - size: bytes.length - }; - } finally { - await handle.close(); + assertArchiveRef(operation.target, `Archive transaction operation ${operation.id} target`); + for (const field2 of ["source", "staged", "backup"]) { + if (operation[field2] !== void 0) { + assertArchiveRef( + operation[field2], + `Archive transaction operation ${operation.id} ${field2}` + ); + } + } + if (operation.expectedTargetHash !== null) { + assertHash( + operation.expectedTargetHash, + `Archive transaction operation ${operation.id} expectedTargetHash` + ); + } + if (operation.type === "write") { + if (operation.staged === void 0 || operation.source !== void 0 || operation.expectedSourceHash !== void 0) { + throw new Error( + `Archive write operation ${operation.id} requires staged and forbids source` + ); + } + assertHash(operation.stagedHash, `Archive write operation ${operation.id} stagedHash`); + if (operation.expectedTargetHash === null !== (operation.backup === void 0)) { + throw new Error( + `Archive write operation ${operation.id} backup must match target existence` + ); + } + } else if (operation.type === "remove") { + if (operation.source !== void 0 || operation.staged !== void 0 || operation.stagedHash !== void 0 || operation.expectedSourceHash !== void 0 || operation.backup === void 0 || operation.expectedTargetHash === null) { + throw new Error( + `Archive remove operation ${operation.id} requires a bound target and backup` + ); + } + } else { + if (operation.source === void 0 || operation.staged !== void 0 || operation.stagedHash !== void 0 || operation.backup !== void 0 || operation.expectedTargetHash !== null) { + throw new Error( + `Archive move operation ${operation.id} requires source and an absent target` + ); + } + assertHash( + operation.expectedSourceHash, + `Archive move operation ${operation.id} expectedSourceHash` + ); + } + return operation; + }); + const operationIds = operations.map((operation) => operation.id); + if (new Set(operationIds).size !== operationIds.length) { + throw new Error("Native Archive transaction operation ids must be unique"); } -} -async function readNativeProtectedTextFile(options) { - const snapshot2 = await readNativeProtectedFile(options); - let text3; - try { - text3 = new TextDecoder2("utf-8", { fatal: true }).decode(snapshot2.bytes); - } catch (error) { - throw new Error(`${options.label} is not valid UTF-8`, { cause: error }); + const transactionPrefix = `runtime/transactions/${journal.id}`; + const archiveMoves = operations.filter((operation) => operation.type === "move"); + if (archiveMoves.length !== 1 || archiveMoves[0].id !== "archive-change" || archiveMoves[0].source !== `changes/${journal.change}` || !new RegExp(`^archive/\\d{4}-\\d{2}-\\d{2}-${journal.change}$`, "u").test( + archiveMoves[0].target + ) || operations.at(-1) !== archiveMoves[0]) { + throw new Error("Native Archive transaction must end with its exact change move"); } - return { ...snapshot2, text: text3 }; -} -async function readNativeProtectedDirectory(options) { - const chain = await captureDirectoryChain2(options.root, options.directory, options.label); - let entries; - if (options.maxEntries === void 0) { - entries = await fs9.readdir(options.directory, { withFileTypes: true }); - } else { - const maxEntries = positiveLimit2(options.maxEntries); - entries = []; - const directory = await fs9.opendir(options.directory); - try { - for await (const entry2 of directory) { - entries.push(entry2); - if (entries.length > maxEntries) { - throw new Error(`${options.label} exceeds ${maxEntries} entries`); - } - } - } finally { - await directory.close().catch((error) => { - if (error.code !== "ERR_DIR_CLOSED") throw error; - }); + const specTargets = /* @__PURE__ */ new Set(); + for (const operation of operations.slice(0, -1)) { + if (operation.type === "move" || !/^specs\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/spec\.md$/u.test(operation.target) || specTargets.has(operation.target)) { + throw new Error(`Native Archive transaction spec target is invalid: ${operation.target}`); + } + specTargets.add(operation.target); + if (operation.staged !== void 0 && !operation.staged.startsWith(`${transactionPrefix}/staged/specs/`)) { + throw new Error(`Native Archive transaction staged ref is invalid: ${operation.staged}`); + } + if (operation.backup !== void 0 && !operation.backup.startsWith(`${transactionPrefix}/backups/specs/`)) { + throw new Error(`Native Archive transaction backup ref is invalid: ${operation.backup}`); } } - await verifyDirectoryChain3(chain, options.label); return { - entries, - verify: () => verifyDirectoryChain3(chain, options.label) + schema: "comet.native.transaction.v2", + id: journal.id, + kind: "archive", + status: journal.status, + change: journal.change, + createdAt: journal.createdAt, + preflightHash: journal.preflightHash, + operations }; } -async function captureNativeProtectedDirectoryGuard(options) { - const chain = await captureDirectoryChain2(options.root, options.directory, options.label); - return { verify: () => verifyDirectoryChain3(chain, options.label) }; -} -async function quarantineNativeProtectedDirectoryInternal(options) { - const directory = path11.resolve(options.directory); - const quarantine = path11.resolve(options.quarantine); - if (path11.dirname(quarantine) !== path11.dirname(directory) || !isInside5(path11.resolve(options.root), quarantine) || quarantine === directory) { - throw new Error(`${options.label} quarantine must be a distinct sibling inside its root`); - } - const parentChain = await captureDirectoryChain2( - options.root, - path11.dirname(directory), - options.label - ); - const identity = await captureDirectoryIdentity2(directory, options.label); - if (!isInside5(parentChain[0].realPath, identity.realPath)) { - throw new Error(`${options.label} resolves outside its managed root`); +function parseJournal(value) { + const journal = record(value, "Native transaction journal"); + if (journal.schema === "comet.native.transaction.v2") { + return parseNativeArchiveTransactionJournalV2(journal); } - await options.beforeQuarantine?.(); - await verifyDirectoryChain3(parentChain, options.label); - const current = await fs9.lstat(directory); - if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs9.realpath(directory) !== identity.realPath) { - throw new Error(`${options.label} changed before quarantine`); + rejectUnknown(journal, JOURNAL_KEYS, "Native transaction journal"); + if (journal.schema !== "comet.native.transaction.v1") { + throw new Error("Unsupported Native transaction schema"); } - try { - await fs9.lstat(quarantine); - throw new Error(`${options.label} quarantine path is occupied`); - } catch (error) { - if (error.code !== "ENOENT") throw error; + if (typeof journal.id !== "string" || !/^[a-f0-9-]{8,}$/u.test(journal.id)) { + throw new Error("Native transaction id is invalid"); } - await fs9.rename(directory, quarantine); - await verifyDirectoryChain3(parentChain, options.label); - const quarantined = await fs9.lstat(quarantine); - if (!quarantined.isDirectory() || quarantined.isSymbolicLink() || !sameDirectoryIdentity3(identity, quarantined)) { - throw new Error(`${options.label} changed while quarantining`); + if (journal.kind !== "archive" && journal.kind !== "root-move") { + throw new Error("Native transaction kind is invalid"); } - await options.afterQuarantine?.(quarantine); - try { - await fs9.lstat(directory); - throw new Error(`${options.label} path was recreated while quarantining`); - } catch (error) { - if (error.code !== "ENOENT") throw error; + if (typeof journal.status !== "string" || !TRANSACTION_STATUSES.has(journal.status)) { + throw new Error("Native transaction status is invalid"); } - return { quarantine, parentChain, identity }; -} -async function quarantineNativeProtectedDirectory(options) { - await quarantineNativeProtectedDirectoryInternal(options); -} -async function removeNativeProtectedFile(options) { - const file = path11.resolve(options.file); - const parentChain = await captureDirectoryChain2(options.root, path11.dirname(file), options.label); - const before = await fs9.lstat(file); - if (!before.isFile() || before.isSymbolicLink()) { - throw new Error(`${options.label} must be a regular file`); + if (typeof journal.projectRoot !== "string" || !path10.isAbsolute(journal.projectRoot) || typeof journal.nativeRoot !== "string" || !path10.isAbsolute(journal.nativeRoot)) { + throw new Error("Native transaction roots must be absolute paths"); } - const identity = asFileIdentity(before); - const realPath = await fs9.realpath(file); - if (!isInside5(parentChain[0].realPath, realPath)) { - throw new Error(`${options.label} resolves outside its managed root`); + if (journal.change !== void 0 && (typeof journal.change !== "string" || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(journal.change))) { + throw new Error("Native transaction change name is invalid"); } - const snapshot2 = await readNativeProtectedFile({ - root: options.root, - file, - maxBytes: options.maxBytes, - label: options.label - }); - if (snapshot2.hash !== options.expectedHash || snapshot2.size !== options.expectedSize) { - throw new Error(`${options.label} changed before removal`); + if (!validTimestamp(journal.createdAt)) { + throw new Error("Native transaction createdAt is invalid"); } - const [afterRead, afterReadRealPath] = await Promise.all([fs9.lstat(file), fs9.realpath(file)]); - if (!afterRead.isFile() || afterRead.isSymbolicLink() || !sameFileIdentity3(identity, afterRead) || afterReadRealPath !== realPath) { - throw new Error(`${options.label} changed while verifying removal`); + if (!Array.isArray(journal.operations)) { + throw new Error("Native transaction operations must be an array"); } - await options.beforeRemove?.(); - await verifyDirectoryChain3(parentChain, options.label); - const [current, currentRealPath] = await Promise.all([fs9.lstat(file), fs9.realpath(file)]); - if (!current.isFile() || current.isSymbolicLink() || !sameFileIdentity3(identity, current) || currentRealPath !== realPath) { - throw new Error(`${options.label} changed before removal`); + const operations = journal.operations.map(parseOperation); + const operationIds = operations.map((operation) => operation.id); + if (new Set(operationIds).size !== operationIds.length) { + throw new Error("Native transaction operation ids must be unique"); } - await fs9.rm(file); - await verifyDirectoryChain3(parentChain, options.label); + return { + schema: "comet.native.transaction.v1", + id: journal.id, + kind: journal.kind, + status: journal.status, + projectRoot: journal.projectRoot, + nativeRoot: journal.nativeRoot, + ...typeof journal.change === "string" ? { change: journal.change } : {}, + createdAt: journal.createdAt, + operations + }; } -async function removeNativeProtectedEmptyDirectory(options) { - const directory = path11.resolve(options.directory); - const parentChain = await captureDirectoryChain2( - options.root, - path11.dirname(directory), - options.label - ); - const identity = await captureDirectoryIdentity2(directory, options.label); - if (!isInside5(parentChain[0].realPath, identity.realPath)) { - throw new Error(`${options.label} resolves outside its managed root`); +function parseEvent(value, line) { + const event = record(value, `Native transaction event at line ${line}`); + rejectUnknown(event, EVENT_KEYS, `Native transaction event at line ${line}`); + if (event.sequence !== line) { + throw new Error(`Native transaction event sequence at line ${line} must be ${line}`); } - await options.beforeRemove?.(); - await verifyDirectoryChain3(parentChain, options.label); - const current = await fs9.lstat(directory); - if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(identity, current) || await fs9.realpath(directory) !== identity.realPath) { - throw new Error(`${options.label} changed before removal`); + if (!validTimestamp(event.timestamp)) { + throw new Error(`Native transaction event timestamp at line ${line} is invalid`); } - await fs9.rmdir(directory); - await verifyDirectoryChain3(parentChain, options.label); -} -async function ensureNativeProtectedDirectory(options) { - const lexicalRoot = path11.resolve(options.root); - const lexicalDirectory = path11.resolve(options.directory); - if (!isInside5(lexicalRoot, lexicalDirectory)) { - throw new Error(`${options.label} is outside its managed root`); + if (typeof event.type !== "string" || !EVENT_TYPES.has(event.type)) { + throw new Error(`Native transaction event type at line ${line} is invalid`); } - const chain = [await captureDirectoryIdentity2(lexicalRoot, options.label)]; - let cursor = lexicalRoot; - for (const segment of path11.relative(lexicalRoot, lexicalDirectory).split(path11.sep).filter(Boolean)) { - await verifyDirectoryChain3(chain, options.label); - cursor = path11.join(cursor, segment); - try { - await fs9.mkdir(cursor); - } catch (error) { - if (error.code !== "EEXIST") throw error; - } - const identity = await captureDirectoryIdentity2(cursor, options.label); - if (!isInside5(chain[0].realPath, identity.realPath)) { - throw new Error(`${options.label} resolves outside its managed root: ${cursor}`); - } - chain.push(identity); + const operationEvent = event.type === "operation-started" || event.type === "operation-completed"; + if (operationEvent && (typeof event.operationId !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(event.operationId) || Buffer.byteLength(event.operationId, "utf8") > 256) || !operationEvent && event.operationId !== void 0) { + throw new Error(`Native transaction event operationId at line ${line} is invalid`); } - await verifyDirectoryChain3(chain, options.label); + return { + sequence: event.sequence, + timestamp: event.timestamp, + type: event.type, + ...typeof event.operationId === "string" ? { operationId: event.operationId } : {} + }; } -async function moveNativeProtectedDirectory(options) { - const root = path11.resolve(options.root); - const source = path11.resolve(options.source); - const target = path11.resolve(options.target); - if (source === target || !isInside5(root, source) || !isInside5(root, target) || isInside5(source, target) || isInside5(target, source)) { - throw new Error(`${options.label} source and target must be distinct paths inside one root`); +function matchesIsoTimestampPrefix(value) { + if (value.length > 24) return false; + const shape = "0000-00-00T00:00:00.000Z"; + for (let index = 0; index < value.length; index += 1) { + const expected = shape[index]; + const actual = value[index]; + if (expected === "0") { + if (!/[0-9]/u.test(actual)) return false; + } else if (actual !== expected) { + return false; + } } - await ensureNativeProtectedDirectory({ - root, - directory: path11.dirname(target), - label: `${options.label} target parent` - }); - const sourceParentChain = await captureDirectoryChain2(root, path11.dirname(source), options.label); - const targetParentChain = await captureDirectoryChain2(root, path11.dirname(target), options.label); - const sourceIdentity = await captureDirectoryIdentity2(source, options.label); - if (!isInside5(sourceParentChain[0].realPath, sourceIdentity.realPath)) { - throw new Error(`${options.label} source resolves outside its managed root`); + const numericRanges = [ + [5, 2, 1, 12], + [8, 2, 1, 31], + [11, 2, 0, 23], + [14, 2, 0, 59], + [17, 2, 0, 59], + [20, 3, 0, 999] + ]; + for (const [offset, width, minimum, maximum] of numericRanges) { + if (value.length <= offset) continue; + const partial = value.slice(offset, Math.min(offset + width, value.length)); + const completable = Array.from( + { length: maximum - minimum + 1 }, + (_entry, index) => String(minimum + index).padStart(width, "0") + ).some((candidate) => candidate.startsWith(partial)); + if (!completable) return false; } - try { - await fs9.lstat(target); - throw new Error(`${options.label} target already exists`); - } catch (error) { - if (error.code !== "ENOENT") throw error; + if (value.length >= 10 && !validTimestamp(`${value.slice(0, 10)}T00:00:00.000Z`)) { + return false; } - await options.beforeMove?.(); - await Promise.all([ - verifyDirectoryChain3(sourceParentChain, options.label), - verifyDirectoryChain3(targetParentChain, options.label) - ]); - const current = await fs9.lstat(source); - if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, current) || await fs9.realpath(source) !== sourceIdentity.realPath) { - throw new Error(`${options.label} source changed before move`); + return value.length < 24 || validTimestamp(value); +} +function isStrictLiteralPrefix(value, expected) { + return value.length < expected.length && expected.startsWith(value); +} +function couldCompleteOperationEventSuffix(value) { + const operationPrefix = '","operationId":"'; + if (isStrictLiteralPrefix(value, operationPrefix)) return true; + if (!value.startsWith(operationPrefix)) return false; + const remainder = value.slice(operationPrefix.length); + const closingQuote = remainder.indexOf('"'); + if (closingQuote === -1) { + return remainder.length === 0 || /^[a-z0-9][a-z0-9-]*$/u.test(remainder); } - await fs9.rename(source, target); - await Promise.all([ - verifyDirectoryChain3(sourceParentChain, options.label), - verifyDirectoryChain3(targetParentChain, options.label) - ]); - const moved = await fs9.lstat(target); - if (!moved.isDirectory() || moved.isSymbolicLink() || !sameDirectoryIdentity3(sourceIdentity, moved) || !isInside5(sourceParentChain[0].realPath, await fs9.realpath(target))) { - throw new Error(`${options.label} source identity changed while moving`); + const operationId = remainder.slice(0, closingQuote); + if (!/^[a-z0-9][a-z0-9-]*$/u.test(operationId)) return false; + return isStrictLiteralPrefix(remainder.slice(closingQuote), '"}'); +} +function isRecognizedIncompleteEventTail(source, sequence) { + if (source.length === 0 || Buffer.byteLength(source, "utf8") > NATIVE_TRANSACTION_EVENT_MAX_BYTES) { + return false; } - try { - await fs9.lstat(source); - throw new Error(`${options.label} source was recreated while moving`); - } catch (error) { - if (error.code !== "ENOENT") throw error; + const prefix = `{"sequence":${sequence},"timestamp":"`; + if (isStrictLiteralPrefix(source, prefix)) return true; + if (!source.startsWith(prefix)) return false; + const afterPrefix = source.slice(prefix.length); + const timestamp3 = afterPrefix.slice(0, Math.min(24, afterPrefix.length)); + if (!matchesIsoTimestampPrefix(timestamp3)) return false; + if (afterPrefix.length < 24) return true; + if (!validTimestamp(timestamp3)) return false; + const typePrefix = '","type":"'; + const afterTimestamp = afterPrefix.slice(24); + if (isStrictLiteralPrefix(afterTimestamp, typePrefix)) return true; + if (!afterTimestamp.startsWith(typePrefix)) return false; + const typeAndSuffix = afterTimestamp.slice(typePrefix.length); + for (const type of EVENT_TYPES) { + if (isStrictLiteralPrefix(typeAndSuffix, type)) return true; + if (!typeAndSuffix.startsWith(type)) continue; + const suffix = typeAndSuffix.slice(type.length); + if (type === "operation-started" || type === "operation-completed") { + if (couldCompleteOperationEventSuffix(suffix)) return true; + } else if (isStrictLiteralPrefix(suffix, '"}')) { + return true; + } } + return false; } -async function copyNativeProtectedFile(options) { - const source = await readNativeProtectedFile({ - root: options.sourceRoot, - file: options.source, - maxBytes: options.maxBytes, - label: options.label, - hooks: options.hooks, - forbiddenRoots: options.forbiddenRoots - }); - if (options.expectedHash !== void 0 && source.hash !== options.expectedHash) { - throw new Error(`${options.label} content changed before copy`); +function parseEventLine(source, line) { + if (source.length === 0) throw new Error("Blank transaction event line"); + if (Buffer.byteLength(source, "utf8") > NATIVE_TRANSACTION_EVENT_MAX_BYTES) { + throw new Error(`Native transaction event at line ${line} is too large`); } - await atomicWriteBytes(options.target, source.bytes, { - containedRoot: options.targetRoot, - exclusive: options.exclusive, - beforeCommit: async () => { - await options.hooks?.beforeTargetCommit?.(); - if (options.expectedTargetHash === void 0) return; - if (options.expectedTargetHash === null) { + return parseEvent(JSON.parse(source), line); +} +function parseEventLogSource(source) { + const entries = source.split("\n"); + const terminated = entries.at(-1) === ""; + if (terminated) entries.pop(); + if (entries.length > NATIVE_TRANSACTION_EVENT_MAX_COUNT) { + throw new Error( + `Native transaction event log exceeds ${NATIVE_TRANSACTION_EVENT_MAX_COUNT} events` + ); + } + const events = []; + for (let index = 0; index < entries.length; index += 1) { + const line = index + 1; + const raw = entries[index]; + const entry2 = raw.endsWith("\r") ? raw.slice(0, -1) : raw; + const finalUnterminated = !terminated && index === entries.length - 1; + try { + events.push(parseEventLine(entry2, line)); + } catch (error) { + if (finalUnterminated) { + let syntacticallyComplete = false; try { - await fs9.lstat(options.target); - } catch (error) { - if (error.code === "ENOENT") return; - throw error; + JSON.parse(entry2); + syntacticallyComplete = true; + } catch { } - throw new Error(`${options.label} target changed before commit`); - } - const target = await readNativeProtectedFile({ - root: options.targetRoot, - file: options.target, - maxBytes: options.maxBytes, - label: `${options.label} existing target` - }); - if (target.hash !== options.expectedTargetHash) { - throw new Error(`${options.label} target changed before commit`); + if (!syntacticallyComplete && isRecognizedIncompleteEventTail(entry2, line)) break; } + throw new Error(`Invalid Native transaction event at line ${line}`, { cause: error }); } - }); - const persisted = await readNativeProtectedFile({ - root: options.targetRoot, - file: options.target, - maxBytes: options.maxBytes, - label: `${options.label} target` - }); - if (persisted.hash !== source.hash || persisted.size !== source.size) { - throw new Error(`${options.label} target could not be verified`); } - return persisted; + return events; } - -// domains/comet-native/native-transaction.ts -var JOURNAL_KEYS = /* @__PURE__ */ new Set([ - "schema", - "id", - "kind", - "status", - "projectRoot", - "nativeRoot", - "change", - "createdAt", - "operations" -]); -var OPERATION_KEYS = /* @__PURE__ */ new Set(["id", "type", "source", "target", "staged", "backup"]); -var ARCHIVE_V2_JOURNAL_KEYS = /* @__PURE__ */ new Set([ - "schema", - "id", - "kind", - "status", - "change", - "createdAt", - "preflightHash", - "operations" -]); -var ARCHIVE_V2_OPERATION_KEYS = /* @__PURE__ */ new Set([ - "id", - "type", - "source", - "target", - "staged", - "backup", - "expectedSourceHash", - "expectedTargetHash", - "stagedHash" -]); -var EVENT_KEYS = /* @__PURE__ */ new Set(["sequence", "timestamp", "type", "operationId"]); -var TRANSACTION_STATUSES = /* @__PURE__ */ new Set([ - "prepared", - "applying", - "committed", - "rolling-back", - "rolled-back" -]); -var EVENT_TYPES = /* @__PURE__ */ new Set([ - "prepared", - "operation-started", - "operation-completed", - "archive-finalization-started", - "archive-finalized", - "commit", - "rollback-started", - "rollback-completed" -]); -var NATIVE_TRANSACTION_JOURNAL_MAX_BYTES = 256 * 1024; -var NATIVE_TRANSACTION_EVENTS_MAX_BYTES = 1024 * 1024; -var NATIVE_TRANSACTION_EVENT_MAX_BYTES = 16 * 1024; -var NATIVE_TRANSACTION_EVENT_MAX_COUNT = 1024; -var NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES = 64 * 1024 * 1024; -var NATIVE_LEGACY_TRANSACTION_DIRECTORY_MAX_ENTRIES = 2e4; -var UTF8_DECODER = new TextDecoder3("utf-8", { fatal: true }); -function record4(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); - } - return value; +function canonicalEventLogSource(events) { + return events.length === 0 ? "" : `${events.map((event) => JSON.stringify(event)).join("\n")} +`; } -function rejectUnknown(value, keys, label) { - const unknown = Object.keys(value).filter((key) => !keys.has(key)); - if (unknown.length > 0) throw new Error(`${label} has unknown field(s): ${unknown.join(", ")}`); +function transactionDir(paths, id) { + if (!/^[a-f0-9-]{8,}$/u.test(id)) throw new Error(`Invalid Native transaction id: ${id}`); + return path10.join(paths.transactionsDir, id); } -function validTimestamp(value) { - if (typeof value !== "string") return false; - const parsed = new Date(value); - return !Number.isNaN(parsed.valueOf()) && parsed.toISOString() === value; +function nativeTransactionPaths(paths, id) { + const directory = transactionDir(paths, id); + return { + directory, + journal: path10.join(directory, "transaction.json"), + events: path10.join(directory, "events.jsonl"), + staged: path10.join(directory, "staged"), + backups: path10.join(directory, "backups") + }; } -function assertRef(ref, label) { - if (typeof ref !== "string" || ref.length === 0 || path12.isAbsolute(ref) || /^(?:[A-Za-z]:|~|[\\/])/u.test(ref) || ref.split(/[\\/]/u).includes("..")) { - throw new Error(`${label} must stay inside the Native root`); - } +async function resolveNativeTransactionPaths(paths, id) { + const transaction = nativeTransactionPaths(paths, id); + await Promise.all( + Object.values(transaction).map( + (target) => resolveContainedNativePath(paths.nativeRoot, target) + ) + ); + return transaction; } -function assertHash(value, label) { - if (typeof value !== "string" || !/^[a-f0-9]{64}$/u.test(value)) { - throw new Error(`${label} must be a SHA-256 hash`); +function resolveRefLexically(paths, ref) { + if (ref.length === 0 || path10.isAbsolute(ref) || /^(?:[A-Za-z]:|~|[\\/])/u.test(ref) || ref.split(/[\\/]/u).includes("..")) { + throw new Error(`Unsafe Native transaction ref: ${ref}`); } + const target = path10.resolve(paths.nativeRoot, ...ref.split(/[\\/]/u)); + if (!isInsidePath(paths.nativeRoot, target)) + throw new Error(`Unsafe Native transaction ref: ${ref}`); + return target; } -function assertArchiveRef(ref, label) { - assertRef(ref, label); - if (ref.includes("\\") || ref !== path12.posix.normalize(ref) || ref.split("/").includes(".") || ref.endsWith("/") || Buffer.byteLength(ref, "utf8") > 1024) { - throw new Error(`${label} must be a normalized Native-relative ref`); - } +async function resolveRef(paths, ref) { + return resolveContainedNativePath(paths.nativeRoot, resolveRefLexically(paths, ref)); } -function parseOperation(value, index) { - const operation = record4(value, `transaction operations[${index}]`); - rejectUnknown(operation, OPERATION_KEYS, `transaction operations[${index}]`); - if (typeof operation.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(operation.id)) { - throw new Error(`transaction operations[${index}].id is invalid`); - } - if (operation.type !== "write" && operation.type !== "remove" && operation.type !== "move") { - throw new Error(`transaction operation ${operation.id} has an invalid type`); +async function exists(file) { + try { + await fs8.access(file); + return true; + } catch (error) { + if (error.code === "ENOENT") return false; + throw error; } - assertRef(operation.target, `transaction operation ${operation.id} target`); - for (const field2 of ["source", "staged", "backup"]) { - if (operation[field2] !== void 0) { - assertRef(operation[field2], `transaction operation ${operation.id} ${field2}`); +} +async function readEventLogSnapshot(paths, id, options = {}) { + const tx = await resolveNativeTransactionPaths(paths, id); + try { + await fs8.lstat(tx.events); + } catch (error) { + if (error.code === "ENOENT") { + return { + exists: false, + hash: null, + size: 0, + events: [], + canonicalSource: "", + needsRepair: false + }; } + throw error; } - if (operation.type === "write") { - if (operation.staged === void 0 || operation.source !== void 0) { - throw new Error(`write operation ${operation.id} requires staged and forbids source`); + try { + const snapshot2 = await readNativeProtectedFile({ + root: paths.nativeRoot, + file: tx.events, + maxBytes: NATIVE_TRANSACTION_EVENTS_MAX_BYTES, + label: `Native transaction event log ${id}`, + hooks: options.hooks + }); + let source; + try { + source = UTF8_DECODER.decode(snapshot2.bytes); + } catch (error) { + throw new Error(`Native transaction event log ${id} is not valid UTF-8`, { cause: error }); } - } else if (operation.type === "remove") { - if (operation.source !== void 0 || operation.staged !== void 0) { - throw new Error(`remove operation ${operation.id} forbids source and staged`); + const events = parseEventLogSource(source); + const canonicalSource = canonicalEventLogSource(events); + return { + exists: true, + hash: snapshot2.hash, + size: snapshot2.size, + events, + canonicalSource, + needsRepair: source !== canonicalSource + }; + } catch (error) { + if (error.code === "ENOENT") { + throw new Error(`Native transaction event log ${id} changed while reading`, { + cause: error + }); } - } else if (operation.source === void 0 || operation.staged !== void 0 || operation.backup !== void 0) { - throw new Error(`move operation ${operation.id} requires source and forbids staged and backup`); + throw error; } - return operation; } -function parseNativeArchiveTransactionJournalV2(value) { - const journal = record4(value, "Native Archive transaction journal"); - rejectUnknown(journal, ARCHIVE_V2_JOURNAL_KEYS, "Native Archive transaction journal"); - if (journal.schema !== "comet.native.transaction.v2") { - throw new Error("Unsupported Native Archive transaction schema"); +async function assertEventLogSnapshotUnchanged(paths, id, expected) { + const actual = await readEventLogSnapshot(paths, id); + if (actual.exists !== expected.exists || actual.hash !== expected.hash || actual.size !== expected.size) { + throw new Error(`Native transaction event log ${id} changed before append`); } - if (typeof journal.id !== "string" || !/^[a-f0-9-]{8,}$/u.test(journal.id)) { - throw new Error("Native Archive transaction id is invalid"); +} +async function appendNativeTransactionEvent(paths, id, type, operationId) { + const tx = await resolveNativeTransactionPaths(paths, id); + const snapshot2 = await readEventLogSnapshot(paths, id); + const existing = snapshot2.events.find( + (event2) => event2.type === type && event2.operationId === operationId + ); + if (existing) { + if (snapshot2.needsRepair) { + await atomicWriteText(tx.events, snapshot2.canonicalSource, { + containedRoot: paths.nativeRoot, + beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2) + }); + } + return existing; } - if (journal.kind !== "archive") throw new Error("Native v2 transaction kind must be archive"); - if (typeof journal.status !== "string" || !TRANSACTION_STATUSES.has(journal.status)) { - throw new Error("Native Archive transaction status is invalid"); + if (snapshot2.events.length >= NATIVE_TRANSACTION_EVENT_MAX_COUNT) { + throw new Error( + `Native transaction event log ${id} exceeds ${NATIVE_TRANSACTION_EVENT_MAX_COUNT} events` + ); } - if (typeof journal.change !== "string" || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(journal.change)) { - throw new Error("Native Archive transaction change name is invalid"); + const event = { + sequence: snapshot2.events.length + 1, + timestamp: (/* @__PURE__ */ new Date()).toISOString(), + type, + ...operationId ? { operationId } : {} + }; + parseEvent(event, event.sequence); + await fs8.mkdir(tx.directory, { recursive: true }); + await atomicWriteText(tx.events, canonicalEventLogSource([...snapshot2.events, event]), { + containedRoot: paths.nativeRoot, + beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2) + }); + return event; +} +async function createNativeTransaction(paths, journal) { + if (journal.schema !== "comet.native.transaction.v1") { + throw new Error("Native Archive v2 transactions require the content-bound transaction API"); } - if (!validTimestamp(journal.createdAt)) { - throw new Error("Native Archive transaction createdAt is invalid"); + journal = parseJournal(journal); + const tx = await resolveNativeTransactionPaths(paths, journal.id); + await fs8.mkdir(tx.staged, { recursive: true }); + await fs8.mkdir(tx.backups, { recursive: true }); + await atomicWriteJson(tx.journal, journal); + await appendNativeTransactionEvent(paths, journal.id, "prepared"); +} +async function readNativeTransaction(paths, id, options = {}) { + const tx = await resolveNativeTransactionPaths(paths, id); + const snapshot2 = await readNativeProtectedFile({ + root: paths.nativeRoot, + file: tx.journal, + maxBytes: NATIVE_TRANSACTION_JOURNAL_MAX_BYTES, + label: `Native transaction journal ${id}`, + hooks: options.hooks + }); + const value = JSON.parse(UTF8_DECODER.decode(snapshot2.bytes)); + const journal = parseJournal(value); + if (journal.id !== id) { + throw new Error(`Invalid Native transaction journal: ${id}`); } - assertHash(journal.preflightHash, "Native Archive transaction preflightHash"); - if (!Array.isArray(journal.operations) || journal.operations.length > 65) { - throw new Error("Native Archive transaction operations must be an array"); + return journal; +} +async function readNativeTransactionEvents(paths, id, options = {}) { + return (await readEventLogSnapshot(paths, id, options)).events; +} +async function setNativeTransactionStatus(paths, journal, status) { + if (journal.schema !== "comet.native.transaction.v1") { + throw new Error("Native Archive v2 transactions require the content-bound transaction API"); } - const operations = journal.operations.map((value2, index) => { - const operation = record4(value2, `Archive transaction operations[${index}]`); - rejectUnknown(operation, ARCHIVE_V2_OPERATION_KEYS, `Archive transaction operations[${index}]`); - if (typeof operation.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(operation.id)) { - throw new Error(`Archive transaction operations[${index}].id is invalid`); - } - if (operation.type !== "write" && operation.type !== "remove" && operation.type !== "move") { - throw new Error(`Archive transaction operation ${operation.id} has an invalid type`); - } - assertArchiveRef(operation.target, `Archive transaction operation ${operation.id} target`); - for (const field2 of ["source", "staged", "backup"]) { - if (operation[field2] !== void 0) { - assertArchiveRef( - operation[field2], - `Archive transaction operation ${operation.id} ${field2}` - ); - } - } - if (operation.expectedTargetHash !== null) { - assertHash( - operation.expectedTargetHash, - `Archive transaction operation ${operation.id} expectedTargetHash` - ); - } - if (operation.type === "write") { - if (operation.staged === void 0 || operation.source !== void 0 || operation.expectedSourceHash !== void 0) { - throw new Error( - `Archive write operation ${operation.id} requires staged and forbids source` - ); - } - assertHash(operation.stagedHash, `Archive write operation ${operation.id} stagedHash`); - if (operation.expectedTargetHash === null !== (operation.backup === void 0)) { - throw new Error( - `Archive write operation ${operation.id} backup must match target existence` - ); - } - } else if (operation.type === "remove") { - if (operation.source !== void 0 || operation.staged !== void 0 || operation.stagedHash !== void 0 || operation.expectedSourceHash !== void 0 || operation.backup === void 0 || operation.expectedTargetHash === null) { - throw new Error( - `Archive remove operation ${operation.id} requires a bound target and backup` - ); - } - } else { - if (operation.source === void 0 || operation.staged !== void 0 || operation.stagedHash !== void 0 || operation.backup !== void 0 || operation.expectedTargetHash !== null) { - throw new Error( - `Archive move operation ${operation.id} requires source and an absent target` - ); - } - assertHash( - operation.expectedSourceHash, - `Archive move operation ${operation.id} expectedSourceHash` - ); - } - return operation; + const updated = parseJournal({ ...journal, status }); + await atomicWriteJson((await resolveNativeTransactionPaths(paths, journal.id)).journal, updated); + return updated; +} +async function readLegacyTransactionFile(paths, file, label) { + try { + return await readNativeProtectedFile({ + root: paths.nativeRoot, + file, + maxBytes: NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES, + label + }); + } catch (error) { + if (error.code === "ENOENT") return null; + throw error; + } +} +async function copyAtomic(paths, source, target, label) { + const sourceSnapshot = await readLegacyTransactionFile(paths, source, `${label} source`); + if (!sourceSnapshot) throw new Error(`${label} source does not exist`); + const targetSnapshot = await readLegacyTransactionFile(paths, target, `${label} target`); + await ensureNativeProtectedDirectory({ + root: paths.nativeRoot, + directory: path10.dirname(target), + label: `${label} target parent` }); - const operationIds = operations.map((operation) => operation.id); - if (new Set(operationIds).size !== operationIds.length) { - throw new Error("Native Archive transaction operation ids must be unique"); + await copyNativeProtectedFile({ + sourceRoot: paths.nativeRoot, + source, + targetRoot: paths.nativeRoot, + target, + maxBytes: NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES, + label, + expectedHash: sourceSnapshot.hash, + expectedTargetHash: targetSnapshot?.hash ?? null, + exclusive: targetSnapshot === null + }); +} +async function removeLegacyTransactionFile(paths, file, label) { + const snapshot2 = await readLegacyTransactionFile(paths, file, label); + if (!snapshot2) return; + await removeNativeProtectedFile({ + root: paths.nativeRoot, + file, + maxBytes: NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES, + expectedHash: snapshot2.hash, + expectedSize: snapshot2.size, + label + }); +} +async function backupTarget(paths, operation) { + if (!operation.backup) return; + const target = await resolveRef(paths, operation.target); + const backup = await resolveRef(paths, operation.backup); + if (!await exists(target) || await exists(backup)) return; + await copyAtomic(paths, target, backup, `Legacy Native transaction backup ${operation.id}`); +} +async function applyOperation(paths, operation) { + const target = await resolveRef(paths, operation.target); + if (operation.type === "write") { + if (!operation.staged) throw new Error(`Write operation ${operation.id} has no staged ref`); + await backupTarget(paths, operation); + await copyAtomic( + paths, + await resolveRef(paths, operation.staged), + target, + `Legacy Native transaction write ${operation.id}` + ); + return; } - const transactionPrefix = `runtime/transactions/${journal.id}`; - const archiveMoves = operations.filter((operation) => operation.type === "move"); - if (archiveMoves.length !== 1 || archiveMoves[0].id !== "archive-change" || archiveMoves[0].source !== `changes/${journal.change}` || !new RegExp(`^archive/\\d{4}-\\d{2}-\\d{2}-${journal.change}$`, "u").test( - archiveMoves[0].target - ) || operations.at(-1) !== archiveMoves[0]) { - throw new Error("Native Archive transaction must end with its exact change move"); + if (operation.type === "remove") { + await backupTarget(paths, operation); + await removeLegacyTransactionFile( + paths, + target, + `Legacy Native transaction remove ${operation.id}` + ); + return; } - const specTargets = /* @__PURE__ */ new Set(); - for (const operation of operations.slice(0, -1)) { - if (operation.type === "move" || !/^specs\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/spec\.md$/u.test(operation.target) || specTargets.has(operation.target)) { - throw new Error(`Native Archive transaction spec target is invalid: ${operation.target}`); - } - specTargets.add(operation.target); - if (operation.staged !== void 0 && !operation.staged.startsWith(`${transactionPrefix}/staged/specs/`)) { - throw new Error(`Native Archive transaction staged ref is invalid: ${operation.staged}`); - } - if (operation.backup !== void 0 && !operation.backup.startsWith(`${transactionPrefix}/backups/specs/`)) { - throw new Error(`Native Archive transaction backup ref is invalid: ${operation.backup}`); - } + if (!operation.source) throw new Error(`Move operation ${operation.id} has no source ref`); + const source = await resolveRef(paths, operation.source); + const [sourceExists, targetExists] = await Promise.all([exists(source), exists(target)]); + if (!sourceExists && targetExists) { + const targetDirectory = await readNativeProtectedDirectory({ + root: paths.nativeRoot, + directory: target, + label: `Legacy Native transaction move target ${operation.id}`, + maxEntries: NATIVE_LEGACY_TRANSACTION_DIRECTORY_MAX_ENTRIES + }); + await targetDirectory.verify(); + return; } - return { - schema: "comet.native.transaction.v2", - id: journal.id, - kind: "archive", - status: journal.status, - change: journal.change, - createdAt: journal.createdAt, - preflightHash: journal.preflightHash, - operations - }; + if (targetExists) throw new Error(`Move target already exists: ${operation.target}`); + if (!sourceExists) throw new Error(`Move source does not exist: ${operation.source}`); + await moveNativeProtectedDirectory({ + root: paths.nativeRoot, + source, + target, + label: `Legacy Native transaction move ${operation.id}` + }); } -function parseJournal(value) { - const journal = record4(value, "Native transaction journal"); - if (journal.schema === "comet.native.transaction.v2") { - return parseNativeArchiveTransactionJournalV2(journal); - } - rejectUnknown(journal, JOURNAL_KEYS, "Native transaction journal"); +async function applyNativeTransaction(paths, journal, hooks) { if (journal.schema !== "comet.native.transaction.v1") { - throw new Error("Unsupported Native transaction schema"); - } - if (typeof journal.id !== "string" || !/^[a-f0-9-]{8,}$/u.test(journal.id)) { - throw new Error("Native transaction id is invalid"); - } - if (journal.kind !== "archive" && journal.kind !== "root-move") { - throw new Error("Native transaction kind is invalid"); + throw new Error("Native Archive v2 transactions require the content-bound transaction API"); } - if (typeof journal.status !== "string" || !TRANSACTION_STATUSES.has(journal.status)) { - throw new Error("Native transaction status is invalid"); + let current = journal.status === "prepared" ? await setNativeTransactionStatus(paths, journal, "applying") : journal; + const events = await readNativeTransactionEvents(paths, journal.id); + const completed = new Set( + events.filter((event) => event.type === "operation-completed").map((event) => event.operationId) + ); + let completedCount = completed.size; + for (const operation of current.operations) { + if (completed.has(operation.id)) continue; + await appendNativeTransactionEvent(paths, current.id, "operation-started", operation.id); + await applyOperation(paths, operation); + await appendNativeTransactionEvent(paths, current.id, "operation-completed", operation.id); + completedCount += 1; + await hooks?.afterOperation?.(operation, completedCount); } - if (typeof journal.projectRoot !== "string" || !path12.isAbsolute(journal.projectRoot) || typeof journal.nativeRoot !== "string" || !path12.isAbsolute(journal.nativeRoot)) { - throw new Error("Native transaction roots must be absolute paths"); + current = await readNativeTransaction(paths, current.id); + return current; +} +async function rollbackOperation(paths, operation) { + const target = await resolveRef(paths, operation.target); + const backup = operation.backup ? await resolveRef(paths, operation.backup) : null; + if (operation.type === "move") { + if (!operation.source) throw new Error(`Move operation ${operation.id} has no source ref`); + const source = await resolveRef(paths, operation.source); + if (await exists(target)) { + if (await exists(source)) { + throw new Error(`Legacy Native rollback source already exists: ${operation.source}`); + } + await moveNativeProtectedDirectory({ + root: paths.nativeRoot, + source: target, + target: source, + label: `Legacy Native transaction rollback move ${operation.id}` + }); + } + return; } - if (journal.change !== void 0 && (typeof journal.change !== "string" || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(journal.change))) { - throw new Error("Native transaction change name is invalid"); + if (backup && await exists(backup)) { + await copyAtomic( + paths, + backup, + target, + `Legacy Native transaction rollback restore ${operation.id}` + ); + } else { + await removeLegacyTransactionFile( + paths, + target, + `Legacy Native transaction rollback remove ${operation.id}` + ); } - if (!validTimestamp(journal.createdAt)) { - throw new Error("Native transaction createdAt is invalid"); +} +async function rollbackNativeTransaction(paths, journal) { + if (journal.schema !== "comet.native.transaction.v1") { + throw new Error("Native Archive v2 transactions require the content-bound transaction API"); } - if (!Array.isArray(journal.operations)) { - throw new Error("Native transaction operations must be an array"); + const events = await readNativeTransactionEvents(paths, journal.id); + if (events.some( + (event) => event.type === "archive-finalization-started" || event.type === "archive-finalized" + )) { + throw new Error("An archive whose finalization started can only be recovered by continuing it"); } - const operations = journal.operations.map(parseOperation); - const operationIds = operations.map((operation) => operation.id); - if (new Set(operationIds).size !== operationIds.length) { - throw new Error("Native transaction operation ids must be unique"); + let current = await setNativeTransactionStatus(paths, journal, "rolling-back"); + await appendNativeTransactionEvent(paths, current.id, "rollback-started"); + const started = new Set( + events.filter((event) => event.type === "operation-started" || event.type === "operation-completed").map((event) => event.operationId) + ); + for (const operation of [...current.operations].reverse()) { + if (started.has(operation.id)) await rollbackOperation(paths, operation); } - return { - schema: "comet.native.transaction.v1", - id: journal.id, - kind: journal.kind, - status: journal.status, - projectRoot: journal.projectRoot, - nativeRoot: journal.nativeRoot, - ...typeof journal.change === "string" ? { change: journal.change } : {}, - createdAt: journal.createdAt, - operations - }; -} -function parseEvent(value, line) { - const event = record4(value, `Native transaction event at line ${line}`); - rejectUnknown(event, EVENT_KEYS, `Native transaction event at line ${line}`); - if (event.sequence !== line) { - throw new Error(`Native transaction event sequence at line ${line} must be ${line}`); - } - if (!validTimestamp(event.timestamp)) { - throw new Error(`Native transaction event timestamp at line ${line} is invalid`); - } - if (typeof event.type !== "string" || !EVENT_TYPES.has(event.type)) { - throw new Error(`Native transaction event type at line ${line} is invalid`); - } - const operationEvent = event.type === "operation-started" || event.type === "operation-completed"; - if (operationEvent && (typeof event.operationId !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(event.operationId) || Buffer.byteLength(event.operationId, "utf8") > 256) || !operationEvent && event.operationId !== void 0) { - throw new Error(`Native transaction event operationId at line ${line} is invalid`); - } - return { - sequence: event.sequence, - timestamp: event.timestamp, - type: event.type, - ...typeof event.operationId === "string" ? { operationId: event.operationId } : {} - }; + await appendNativeTransactionEvent(paths, current.id, "rollback-completed"); + current = await setNativeTransactionStatus(paths, current, "rolled-back"); + return current; } -function matchesIsoTimestampPrefix(value) { - if (value.length > 24) return false; - const shape = "0000-00-00T00:00:00.000Z"; - for (let index = 0; index < value.length; index += 1) { - const expected = shape[index]; - const actual = value[index]; - if (expected === "0") { - if (!/[0-9]/u.test(actual)) return false; - } else if (actual !== expected) { - return false; - } - } - const numericRanges = [ - [5, 2, 1, 12], - [8, 2, 1, 31], - [11, 2, 0, 23], - [14, 2, 0, 59], - [17, 2, 0, 59], - [20, 3, 0, 999] - ]; - for (const [offset, width, minimum, maximum] of numericRanges) { - if (value.length <= offset) continue; - const partial = value.slice(offset, Math.min(offset + width, value.length)); - const completable = Array.from( - { length: maximum - minimum + 1 }, - (_entry, index) => String(minimum + index).padStart(width, "0") - ).some((candidate) => candidate.startsWith(partial)); - if (!completable) return false; - } - if (value.length >= 10 && !validTimestamp(`${value.slice(0, 10)}T00:00:00.000Z`)) { - return false; +async function finalizeNativeTransaction(paths, journal, event) { + if (journal.schema !== "comet.native.transaction.v1") { + throw new Error("Native Archive v2 transactions require the content-bound transaction API"); } - return value.length < 24 || validTimestamp(value); -} -function isStrictLiteralPrefix(value, expected) { - return value.length < expected.length && expected.startsWith(value); + await appendNativeTransactionEvent(paths, journal.id, event); + return event === "commit" ? setNativeTransactionStatus(paths, journal, "committed") : journal; } -function couldCompleteOperationEventSuffix(value) { - const operationPrefix = '","operationId":"'; - if (isStrictLiteralPrefix(value, operationPrefix)) return true; - if (!value.startsWith(operationPrefix)) return false; - const remainder = value.slice(operationPrefix.length); - const closingQuote = remainder.indexOf('"'); - if (closingQuote === -1) { - return remainder.length === 0 || /^[a-z0-9][a-z0-9-]*$/u.test(remainder); +function nativeRootRef(paths, target) { + const absolute = path10.resolve(target); + if (!isInsidePath(paths.nativeRoot, absolute)) { + throw new Error(`Path is outside the Native root: ${target}`); } - const operationId = remainder.slice(0, closingQuote); - if (!/^[a-z0-9][a-z0-9-]*$/u.test(operationId)) return false; - return isStrictLiteralPrefix(remainder.slice(closingQuote), '"}'); + return path10.relative(paths.nativeRoot, absolute).split(path10.sep).join("/"); } -function isRecognizedIncompleteEventTail(source, sequence) { - if (source.length === 0 || Buffer.byteLength(source, "utf8") > NATIVE_TRANSACTION_EVENT_MAX_BYTES) { - return false; + +// domains/comet-native/native-mutation-lock.ts +async function hasUnfinishedTransaction(paths, allowedTransactionId) { + let entries; + try { + entries = await fs9.readdir(paths.transactionsDir, { withFileTypes: true }); + } catch (error) { + if (error.code === "ENOENT") return false; + throw error; } - const prefix = `{"sequence":${sequence},"timestamp":"`; - if (isStrictLiteralPrefix(source, prefix)) return true; - if (!source.startsWith(prefix)) return false; - const afterPrefix = source.slice(prefix.length); - const timestamp3 = afterPrefix.slice(0, Math.min(24, afterPrefix.length)); - if (!matchesIsoTimestampPrefix(timestamp3)) return false; - if (afterPrefix.length < 24) return true; - if (!validTimestamp(timestamp3)) return false; - const typePrefix = '","type":"'; - const afterTimestamp = afterPrefix.slice(24); - if (isStrictLiteralPrefix(afterTimestamp, typePrefix)) return true; - if (!afterTimestamp.startsWith(typePrefix)) return false; - const typeAndSuffix = afterTimestamp.slice(typePrefix.length); - for (const type of EVENT_TYPES) { - if (isStrictLiteralPrefix(typeAndSuffix, type)) return true; - if (!typeAndSuffix.startsWith(type)) continue; - const suffix = typeAndSuffix.slice(type.length); - if (type === "operation-started" || type === "operation-completed") { - if (couldCompleteOperationEventSuffix(suffix)) return true; - } else if (isStrictLiteralPrefix(suffix, '"}')) { + for (const entry2 of entries) { + if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue; + try { + const transaction = await readNativeTransaction(paths, entry2.name); + if (transaction.id !== allowedTransactionId && transaction.status !== "committed" && transaction.status !== "rolled-back") { + return true; + } + } catch { return true; } } return false; } -function parseEventLine(source, line) { - if (source.length === 0) throw new Error("Blank transaction event line"); - if (Buffer.byteLength(source, "utf8") > NATIVE_TRANSACTION_EVENT_MAX_BYTES) { - throw new Error(`Native transaction event at line ${line} is too large`); - } - return parseEvent(JSON.parse(source), line); -} -function parseEventLogSource(source) { - const entries = source.split("\n"); - const terminated = entries.at(-1) === ""; - if (terminated) entries.pop(); - if (entries.length > NATIVE_TRANSACTION_EVENT_MAX_COUNT) { - throw new Error( - `Native transaction event log exceeds ${NATIVE_TRANSACTION_EVENT_MAX_COUNT} events` - ); - } - const events = []; - for (let index = 0; index < entries.length; index += 1) { - const line = index + 1; - const raw = entries[index]; - const entry2 = raw.endsWith("\r") ? raw.slice(0, -1) : raw; - const finalUnterminated = !terminated && index === entries.length - 1; +async function acquireNativeMutationLock(paths, operation) { + const deadline = Date.now() + 5e3; + const file = path11.join(paths.locksDir, "root-move.lock"); + while (true) { try { - events.push(parseEventLine(entry2, line)); + return await acquireNativeLock(paths, "root-move", operation); } catch (error) { - if (finalUnterminated) { - let syntacticallyComplete = false; - try { - JSON.parse(entry2); - syntacticallyComplete = true; - } catch { - } - if (!syntacticallyComplete && isRecognizedIncompleteEventTail(entry2, line)) break; - } - throw new Error(`Invalid Native transaction event at line ${line}`, { cause: error }); + const cause = error.cause; + if (cause?.code !== "EEXIST") throw error; + const diagnosis = await diagnoseNativeLock(file); + if (diagnosis.status === "missing") continue; + if (diagnosis.status !== "active" || Date.now() >= deadline) throw error; + await new Promise((resolve) => setTimeout(resolve, 5 + Math.floor(Math.random() * 11))); } } - return events; -} -function canonicalEventLogSource(events) { - return events.length === 0 ? "" : `${events.map((event) => JSON.stringify(event)).join("\n")} -`; -} -function transactionDir(paths, id) { - if (!/^[a-f0-9-]{8,}$/u.test(id)) throw new Error(`Invalid Native transaction id: ${id}`); - return path12.join(paths.transactionsDir, id); } -function nativeTransactionPaths(paths, id) { - const directory = transactionDir(paths, id); - return { - directory, - journal: path12.join(directory, "transaction.json"), - events: path12.join(directory, "events.jsonl"), - staged: path12.join(directory, "staged"), - backups: path12.join(directory, "backups") - }; +async function withNativeMutationLock(paths, operation, work, options) { + const lock = await acquireNativeMutationLock(paths, operation); + try { + await assertNoPendingNativeRootMove(paths.projectRoot); + if (await hasUnfinishedTransaction(paths, options?.allowedTransactionId)) { + throw new Error("Native transaction recovery is required before another mutation"); + } + return await work(); + } finally { + await releaseNativeLock(lock); + } } -async function resolveNativeTransactionPaths(paths, id) { - const transaction = nativeTransactionPaths(paths, id); - await Promise.all( - Object.values(transaction).map( - (target) => resolveContainedNativePath(paths.nativeRoot, target) - ) - ); - return transaction; + +// domains/comet-native/native-revision.ts +function validRevision(value) { + return Number.isSafeInteger(value) && value >= 1; } -function resolveRefLexically(paths, ref) { - if (ref.length === 0 || path12.isAbsolute(ref) || /^(?:[A-Za-z]:|~|[\\/])/u.test(ref) || ref.split(/[\\/]/u).includes("..")) { - throw new Error(`Unsafe Native transaction ref: ${ref}`); +async function compareAndSwapNativeRevision(options) { + if (!validRevision(options.expectedRevision)) { + throw new Error("Native expected revision must be a positive integer"); } - const target = path12.resolve(paths.nativeRoot, ...ref.split(/[\\/]/u)); - if (!isInsidePath(paths.nativeRoot, target)) - throw new Error(`Unsafe Native transaction ref: ${ref}`); - return target; + if (options.next.revision !== options.expectedRevision + 1) { + throw new Error("Native CAS next revision must increment the expected revision exactly once"); + } + const current = await options.read(); + if (!validRevision(current.revision)) { + throw new Error("Native current revision must be a positive integer"); + } + const equals = options.equals ?? ((left, right) => JSON.stringify(left) === JSON.stringify(right)); + if (current.revision === options.next.revision && equals(current, options.next)) { + return current; + } + if (current.revision !== options.expectedRevision) { + throw options.conflict(current.revision); + } + await options.write(options.next); + return options.next; } -async function resolveRef(paths, ref) { - return resolveContainedNativePath(paths.nativeRoot, resolveRefLexically(paths, ref)); + +// domains/comet-native/native-snapshot.ts +import { createHash as createHash6 } from "crypto"; +import { spawn } from "node:child_process"; +import { promises as fs10 } from "fs"; +import path12 from "path"; + +// domains/comet-native/native-hash.ts +import { createHash as createHash5 } from "crypto"; +import { createReadStream } from "fs"; +async function sha256File(file) { + const hash8 = createHash5("sha256"); + for await (const chunk of createReadStream(file)) hash8.update(chunk); + return hash8.digest("hex"); } -async function exists(file) { - try { - await fs10.access(file); - return true; - } catch (error) { - if (error.code === "ENOENT") return false; - throw error; - } +function sha256Text(content) { + return createHash5("sha256").update(content).digest("hex"); } -async function readEventLogSnapshot(paths, id, options = {}) { - const tx = await resolveNativeTransactionPaths(paths, id); - try { - await fs10.lstat(tx.events); - } catch (error) { - if (error.code === "ENOENT") { - return { - exists: false, - hash: null, - size: 0, - events: [], - canonicalSource: "", - needsRepair: false - }; - } - throw error; - } - try { - const snapshot2 = await readNativeProtectedFile({ - root: paths.nativeRoot, - file: tx.events, - maxBytes: NATIVE_TRANSACTION_EVENTS_MAX_BYTES, - label: `Native transaction event log ${id}`, - hooks: options.hooks - }); - let source; - try { - source = UTF8_DECODER.decode(snapshot2.bytes); - } catch (error) { - throw new Error(`Native transaction event log ${id} is not valid UTF-8`, { cause: error }); - } - const events = parseEventLogSource(source); - const canonicalSource = canonicalEventLogSource(events); - return { - exists: true, - hash: snapshot2.hash, - size: snapshot2.size, - events, - canonicalSource, - needsRepair: source !== canonicalSource - }; - } catch (error) { - if (error.code === "ENOENT") { - throw new Error(`Native transaction event log ${id} changed while reading`, { - cause: error - }); - } - throw error; - } -} -async function assertEventLogSnapshotUnchanged(paths, id, expected) { - const actual = await readEventLogSnapshot(paths, id); - if (actual.exists !== expected.exists || actual.hash !== expected.hash || actual.size !== expected.size) { - throw new Error(`Native transaction event log ${id} changed before append`); - } -} -async function appendNativeTransactionEvent(paths, id, type, operationId) { - const tx = await resolveNativeTransactionPaths(paths, id); - const snapshot2 = await readEventLogSnapshot(paths, id); - const existing = snapshot2.events.find( - (event2) => event2.type === type && event2.operationId === operationId - ); - if (existing) { - if (snapshot2.needsRepair) { - await atomicWriteText(tx.events, snapshot2.canonicalSource, { - containedRoot: paths.nativeRoot, - beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2) - }); - } - return existing; - } - if (snapshot2.events.length >= NATIVE_TRANSACTION_EVENT_MAX_COUNT) { - throw new Error( - `Native transaction event log ${id} exceeds ${NATIVE_TRANSACTION_EVENT_MAX_COUNT} events` - ); - } - const event = { - sequence: snapshot2.events.length + 1, - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - type, - ...operationId ? { operationId } : {} - }; - parseEvent(event, event.sequence); - await fs10.mkdir(tx.directory, { recursive: true }); - await atomicWriteText(tx.events, canonicalEventLogSource([...snapshot2.events, event]), { - containedRoot: paths.nativeRoot, - beforeCommit: () => assertEventLogSnapshotUnchanged(paths, id, snapshot2) - }); - return event; -} -async function createNativeTransaction(paths, journal) { - if (journal.schema !== "comet.native.transaction.v1") { - throw new Error("Native Archive v2 transactions require the content-bound transaction API"); - } - journal = parseJournal(journal); - const tx = await resolveNativeTransactionPaths(paths, journal.id); - await fs10.mkdir(tx.staged, { recursive: true }); - await fs10.mkdir(tx.backups, { recursive: true }); - await atomicWriteJson(tx.journal, journal); - await appendNativeTransactionEvent(paths, journal.id, "prepared"); -} -async function readNativeTransaction(paths, id, options = {}) { - const tx = await resolveNativeTransactionPaths(paths, id); - const snapshot2 = await readNativeProtectedFile({ - root: paths.nativeRoot, - file: tx.journal, - maxBytes: NATIVE_TRANSACTION_JOURNAL_MAX_BYTES, - label: `Native transaction journal ${id}`, - hooks: options.hooks - }); - const value = JSON.parse(UTF8_DECODER.decode(snapshot2.bytes)); - const journal = parseJournal(value); - if (journal.id !== id) { - throw new Error(`Invalid Native transaction journal: ${id}`); - } - return journal; -} -async function readNativeTransactionEvents(paths, id, options = {}) { - return (await readEventLogSnapshot(paths, id, options)).events; -} -async function setNativeTransactionStatus(paths, journal, status) { - if (journal.schema !== "comet.native.transaction.v1") { - throw new Error("Native Archive v2 transactions require the content-bound transaction API"); - } - const updated = parseJournal({ ...journal, status }); - await atomicWriteJson((await resolveNativeTransactionPaths(paths, journal.id)).journal, updated); - return updated; -} -async function readLegacyTransactionFile(paths, file, label) { - try { - return await readNativeProtectedFile({ - root: paths.nativeRoot, - file, - maxBytes: NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES, - label - }); - } catch (error) { - if (error.code === "ENOENT") return null; - throw error; - } -} -async function copyAtomic(paths, source, target, label) { - const sourceSnapshot = await readLegacyTransactionFile(paths, source, `${label} source`); - if (!sourceSnapshot) throw new Error(`${label} source does not exist`); - const targetSnapshot = await readLegacyTransactionFile(paths, target, `${label} target`); - await ensureNativeProtectedDirectory({ - root: paths.nativeRoot, - directory: path12.dirname(target), - label: `${label} target parent` - }); - await copyNativeProtectedFile({ - sourceRoot: paths.nativeRoot, - source, - targetRoot: paths.nativeRoot, - target, - maxBytes: NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES, - label, - expectedHash: sourceSnapshot.hash, - expectedTargetHash: targetSnapshot?.hash ?? null, - exclusive: targetSnapshot === null - }); -} -async function removeLegacyTransactionFile(paths, file, label) { - const snapshot2 = await readLegacyTransactionFile(paths, file, label); - if (!snapshot2) return; - await removeNativeProtectedFile({ - root: paths.nativeRoot, - file, - maxBytes: NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES, - expectedHash: snapshot2.hash, - expectedSize: snapshot2.size, - label - }); -} -async function backupTarget(paths, operation) { - if (!operation.backup) return; - const target = await resolveRef(paths, operation.target); - const backup = await resolveRef(paths, operation.backup); - if (!await exists(target) || await exists(backup)) return; - await copyAtomic(paths, target, backup, `Legacy Native transaction backup ${operation.id}`); -} -async function applyOperation(paths, operation) { - const target = await resolveRef(paths, operation.target); - if (operation.type === "write") { - if (!operation.staged) throw new Error(`Write operation ${operation.id} has no staged ref`); - await backupTarget(paths, operation); - await copyAtomic( - paths, - await resolveRef(paths, operation.staged), - target, - `Legacy Native transaction write ${operation.id}` - ); - return; - } - if (operation.type === "remove") { - await backupTarget(paths, operation); - await removeLegacyTransactionFile( - paths, - target, - `Legacy Native transaction remove ${operation.id}` - ); - return; - } - if (!operation.source) throw new Error(`Move operation ${operation.id} has no source ref`); - const source = await resolveRef(paths, operation.source); - const [sourceExists, targetExists] = await Promise.all([exists(source), exists(target)]); - if (!sourceExists && targetExists) { - const targetDirectory = await readNativeProtectedDirectory({ - root: paths.nativeRoot, - directory: target, - label: `Legacy Native transaction move target ${operation.id}`, - maxEntries: NATIVE_LEGACY_TRANSACTION_DIRECTORY_MAX_ENTRIES - }); - await targetDirectory.verify(); - return; - } - if (targetExists) throw new Error(`Move target already exists: ${operation.target}`); - if (!sourceExists) throw new Error(`Move source does not exist: ${operation.source}`); - await moveNativeProtectedDirectory({ - root: paths.nativeRoot, - source, - target, - label: `Legacy Native transaction move ${operation.id}` - }); -} -async function applyNativeTransaction(paths, journal, hooks) { - if (journal.schema !== "comet.native.transaction.v1") { - throw new Error("Native Archive v2 transactions require the content-bound transaction API"); - } - let current = journal.status === "prepared" ? await setNativeTransactionStatus(paths, journal, "applying") : journal; - const events = await readNativeTransactionEvents(paths, journal.id); - const completed = new Set( - events.filter((event) => event.type === "operation-completed").map((event) => event.operationId) - ); - let completedCount = completed.size; - for (const operation of current.operations) { - if (completed.has(operation.id)) continue; - await appendNativeTransactionEvent(paths, current.id, "operation-started", operation.id); - await applyOperation(paths, operation); - await appendNativeTransactionEvent(paths, current.id, "operation-completed", operation.id); - completedCount += 1; - await hooks?.afterOperation?.(operation, completedCount); - } - current = await readNativeTransaction(paths, current.id); - return current; -} -async function rollbackOperation(paths, operation) { - const target = await resolveRef(paths, operation.target); - const backup = operation.backup ? await resolveRef(paths, operation.backup) : null; - if (operation.type === "move") { - if (!operation.source) throw new Error(`Move operation ${operation.id} has no source ref`); - const source = await resolveRef(paths, operation.source); - if (await exists(target)) { - if (await exists(source)) { - throw new Error(`Legacy Native rollback source already exists: ${operation.source}`); - } - await moveNativeProtectedDirectory({ - root: paths.nativeRoot, - source: target, - target: source, - label: `Legacy Native transaction rollback move ${operation.id}` - }); - } - return; - } - if (backup && await exists(backup)) { - await copyAtomic( - paths, - backup, - target, - `Legacy Native transaction rollback restore ${operation.id}` - ); - } else { - await removeLegacyTransactionFile( - paths, - target, - `Legacy Native transaction rollback remove ${operation.id}` - ); - } -} -async function rollbackNativeTransaction(paths, journal) { - if (journal.schema !== "comet.native.transaction.v1") { - throw new Error("Native Archive v2 transactions require the content-bound transaction API"); - } - const events = await readNativeTransactionEvents(paths, journal.id); - if (events.some( - (event) => event.type === "archive-finalization-started" || event.type === "archive-finalized" - )) { - throw new Error("An archive whose finalization started can only be recovered by continuing it"); - } - let current = await setNativeTransactionStatus(paths, journal, "rolling-back"); - await appendNativeTransactionEvent(paths, current.id, "rollback-started"); - const started = new Set( - events.filter((event) => event.type === "operation-started" || event.type === "operation-completed").map((event) => event.operationId) - ); - for (const operation of [...current.operations].reverse()) { - if (started.has(operation.id)) await rollbackOperation(paths, operation); - } - await appendNativeTransactionEvent(paths, current.id, "rollback-completed"); - current = await setNativeTransactionStatus(paths, current, "rolled-back"); - return current; -} -async function finalizeNativeTransaction(paths, journal, event) { - if (journal.schema !== "comet.native.transaction.v1") { - throw new Error("Native Archive v2 transactions require the content-bound transaction API"); - } - await appendNativeTransactionEvent(paths, journal.id, event); - return event === "commit" ? setNativeTransactionStatus(paths, journal, "committed") : journal; -} -function nativeRootRef(paths, target) { - const absolute = path12.resolve(target); - if (!isInsidePath(paths.nativeRoot, absolute)) { - throw new Error(`Path is outside the Native root: ${target}`); - } - return path12.relative(paths.nativeRoot, absolute).split(path12.sep).join("/"); -} - -// domains/comet-native/native-mutation-lock.ts -async function hasUnfinishedTransaction(paths, allowedTransactionId) { - let entries; - try { - entries = await fs11.readdir(paths.transactionsDir, { withFileTypes: true }); - } catch (error) { - if (error.code === "ENOENT") return false; - throw error; - } - for (const entry2 of entries) { - if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue; - try { - const transaction = await readNativeTransaction(paths, entry2.name); - if (transaction.id !== allowedTransactionId && transaction.status !== "committed" && transaction.status !== "rolled-back") { - return true; - } - } catch { - return true; - } - } - return false; -} -async function acquireNativeMutationLock(paths, operation) { - const deadline = Date.now() + 5e3; - const file = path13.join(paths.locksDir, "root-move.lock"); - while (true) { - try { - return await acquireNativeLock(paths, "root-move", operation); - } catch (error) { - const cause = error.cause; - if (cause?.code !== "EEXIST") throw error; - const diagnosis = await diagnoseNativeLock(file); - if (diagnosis.status === "missing") continue; - if (diagnosis.status !== "active" || Date.now() >= deadline) throw error; - await new Promise((resolve) => setTimeout(resolve, 5 + Math.floor(Math.random() * 11))); - } - } -} -async function withNativeMutationLock(paths, operation, work, options) { - const lock = await acquireNativeMutationLock(paths, operation); - try { - await assertNoPendingNativeRootMove(paths.projectRoot); - if (await hasUnfinishedTransaction(paths, options?.allowedTransactionId)) { - throw new Error("Native transaction recovery is required before another mutation"); - } - return await work(); - } finally { - await releaseNativeLock(lock); - } -} - -// domains/comet-native/native-review-trust.ts -import path14 from "node:path"; - -// domains/comet-native/native-review-contract.ts -var NATIVE_REVIEW_TRUST_POLICY_REF = ".comet/native-review-trust.json"; - -// domains/comet-native/native-review-trust.ts -var NATIVE_REVIEW_TRUST_POLICY_SCHEMA = "comet.native.review-trust-policy.v2"; -var POLICY_HASH_TAG = NATIVE_REVIEW_TRUST_POLICY_SCHEMA; -var MAX_POLICY_BYTES = 64 * 1024; -var HASH_PATTERN4 = /^[a-f0-9]{64}$/u; -function record5(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); - } - return value; -} -function exactKeys4(value, expected, label) { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - if (JSON.stringify(actual) !== JSON.stringify(canonical)) { - throw new Error(`${label} fields are invalid`); - } -} -function identities(value, label) { - if (!Array.isArray(value) || value.length === 0 || value.length > 64) { - throw new Error(`${label} must be a bounded non-empty array`); - } - const parsed = value.map(parseNativeReviewIdentity).sort((left, right) => left.keyId.localeCompare(right.keyId, "en")); - if (new Set(parsed.map((identity) => identity.keyId)).size !== parsed.length || JSON.stringify(value) !== JSON.stringify(parsed)) { - throw new Error(`${label} must be sorted and unique`); - } - return parsed; -} -function buildNativeReviewTrustPolicy(input) { - const content = { - schema: NATIVE_REVIEW_TRUST_POLICY_SCHEMA, - controllerKeyId: input.controllerIdentity.keyId, - implementationKeyId: input.implementationKeyId, - trustedReviewers: [...input.trustedReviewers].sort( - (left, right) => left.keyId.localeCompare(right.keyId, "en") - ), - trustedWaiverSigners: [...input.trustedWaiverSigners].sort( - (left, right) => left.keyId.localeCompare(right.keyId, "en") - ) - }; - const policyHash = canonicalHash(POLICY_HASH_TAG, content); - return parseNativeReviewTrustPolicy({ - ...content, - policyHash, - controllerSignature: signNativeReviewPayloadHash({ - identity: input.controllerIdentity, - privateKey: input.controllerPrivateKey, - payloadHash: policyHash - }) - }); -} -function parseNativeReviewTrustPolicy(value) { - const root = record5(value, "Native review trust policy"); - exactKeys4( - root, - [ - "schema", - "controllerKeyId", - "implementationKeyId", - "trustedReviewers", - "trustedWaiverSigners", - "policyHash", - "controllerSignature" - ], - "Native review trust policy" - ); - if (root.schema !== NATIVE_REVIEW_TRUST_POLICY_SCHEMA || typeof root.controllerKeyId !== "string" || !HASH_PATTERN4.test(root.controllerKeyId) || typeof root.implementationKeyId !== "string" || !HASH_PATTERN4.test(root.implementationKeyId) || typeof root.policyHash !== "string" || !HASH_PATTERN4.test(root.policyHash)) { - throw new Error("Native review trust policy identity or hash is invalid"); - } - const trustedReviewers = identities(root.trustedReviewers, "Native trusted reviewers"); - const trustedWaiverSigners = identities( - root.trustedWaiverSigners, - "Native trusted waiver signers" - ); - if (trustedReviewers.some((identity) => identity.keyId === root.implementationKeyId) || trustedWaiverSigners.some((identity) => identity.keyId === root.implementationKeyId) || (/* @__PURE__ */ new Set([ - root.controllerKeyId, - root.implementationKeyId, - ...trustedReviewers.map((identity) => identity.keyId), - ...trustedWaiverSigners.map((identity) => identity.keyId) - ])).size !== 2 + trustedReviewers.length + trustedWaiverSigners.length) { - throw new Error( - "Native controller, implementation, reviewer, and waiver signer identities must be globally distinct" - ); - } - const content = { - schema: NATIVE_REVIEW_TRUST_POLICY_SCHEMA, - controllerKeyId: root.controllerKeyId, - implementationKeyId: root.implementationKeyId, - trustedReviewers, - trustedWaiverSigners - }; - const policyHash = canonicalHash(POLICY_HASH_TAG, content); - if (policyHash !== root.policyHash) { - throw new Error("Native review trust policy hash mismatch"); - } - const controllerSignature = parseNativeReviewSignature(root.controllerSignature); - if (controllerSignature.keyId !== root.controllerKeyId || controllerSignature.payloadHash !== policyHash) { - throw new Error("Native review trust policy controller signature binding is invalid"); - } - return { ...content, policyHash, controllerSignature }; -} -function verifyNativeReviewTrustPolicy(value, controllerIdentity) { - const policy = parseNativeReviewTrustPolicy(value); - if (policy.controllerKeyId !== controllerIdentity.keyId) { - throw new Error("Native review trust policy controller is not host-trusted"); - } - verifyNativeReviewPayloadHash({ - identity: controllerIdentity, - payloadHash: policy.policyHash, - proof: policy.controllerSignature - }); - return policy; -} -async function readNativeReviewTrustPolicy(paths) { - const file = await readNativeProtectedTextFile({ - root: paths.projectRoot, - file: path14.join(paths.projectRoot, ...NATIVE_REVIEW_TRUST_POLICY_REF.split("/")), - maxBytes: MAX_POLICY_BYTES, - label: NATIVE_REVIEW_TRUST_POLICY_REF - }); - try { - const controllerTrust = await readNativeControllerTrustProject(paths.projectRoot); - if (!controllerTrust) throw new Error("Native project has no controller-owned trust root"); - return verifyNativeReviewTrustPolicy(JSON.parse(file.text), controllerTrust.controllerIdentity); - } catch (error) { - throw new Error("Native review trust policy is not valid canonical JSON", { cause: error }); - } -} -async function loadNativeReviewTrustPolicy(options) { - const baseline = options.scope.baseline.entries.find( - (entry2) => entry2.path === NATIVE_REVIEW_TRUST_POLICY_REF - ); - const current = options.scope.current.entries.find( - (entry2) => entry2.path === NATIVE_REVIEW_TRUST_POLICY_REF - ); - if (!baseline || !current || baseline.hash !== current.hash || baseline.size !== current.size) { - throw new Error( - "Native review trust policy must exist unchanged from change creation through Build" - ); - } - const file = await readNativeProtectedTextFile({ - root: options.paths.projectRoot, - file: path14.join(options.paths.projectRoot, ...NATIVE_REVIEW_TRUST_POLICY_REF.split("/")), - maxBytes: MAX_POLICY_BYTES, - label: NATIVE_REVIEW_TRUST_POLICY_REF - }); - if (file.hash !== current.hash || file.size !== current.size) { - throw new Error("Native review trust policy changed after Build"); - } - let value; - try { - value = JSON.parse(file.text); - } catch (error) { - throw new Error("Native review trust policy is not valid JSON", { cause: error }); - } - const controllerTrust = await readNativeControllerTrustProject(options.paths.projectRoot); - if (!controllerTrust) throw new Error("Native project has no controller-owned trust root"); - return verifyNativeReviewTrustPolicy(value, controllerTrust.controllerIdentity); -} -function trustedNativeIdentity(policy, kind, keyId) { - const identities2 = kind === "reviewer" ? policy.trustedReviewers : policy.trustedWaiverSigners; - const identity = identities2.find((candidate) => candidate.keyId === keyId); - if (!identity) throw new Error(`Native ${kind} identity is not pre-trusted`); - return identity; -} - -// domains/comet-native/native-revision.ts -function validRevision(value) { - return Number.isSafeInteger(value) && value >= 1; -} -async function compareAndSwapNativeRevision(options) { - if (!validRevision(options.expectedRevision)) { - throw new Error("Native expected revision must be a positive integer"); - } - if (options.next.revision !== options.expectedRevision + 1) { - throw new Error("Native CAS next revision must increment the expected revision exactly once"); - } - const current = await options.read(); - if (!validRevision(current.revision)) { - throw new Error("Native current revision must be a positive integer"); - } - const equals = options.equals ?? ((left, right) => JSON.stringify(left) === JSON.stringify(right)); - if (current.revision === options.next.revision && equals(current, options.next)) { - return current; - } - if (current.revision !== options.expectedRevision) { - throw options.conflict(current.revision); - } - await options.write(options.next); - return options.next; -} - -// domains/comet-native/native-snapshot.ts -import { createHash as createHash8 } from "crypto"; -import { spawn } from "node:child_process"; -import { promises as fs12 } from "fs"; -import path15 from "path"; -// domains/comet-native/native-hash.ts -import { createHash as createHash7 } from "crypto"; -import { createReadStream } from "fs"; -async function sha256File(file) { - const hash8 = createHash7("sha256"); - for await (const chunk of createReadStream(file)) hash8.update(chunk); - return hash8.digest("hex"); -} -function sha256Text(content) { - return createHash7("sha256").update(content).digest("hex"); -} +// domains/comet-native/native-review-contract.ts +var NATIVE_REVIEW_TRUST_POLICY_REF = ".comet/native-review-trust.json"; // domains/comet-native/native-snapshot.ts var DEFAULT_NATIVE_SNAPSHOT_LIMITS = { @@ -11342,11 +10560,10 @@ var DEFAULT_NATIVE_SNAPSHOT_LIMITS = { }; var MAX_RECORDED_OMISSIONS = 1e3; var NATIVE_SNAPSHOT_MANIFEST_HARD_MAX_BYTES = 8 * 1024 * 1024; -var CHANGE_NAME_PATTERN3 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; +var CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var MANIFEST_KEYS = /* @__PURE__ */ new Set([ "schema", "origin", - "creation", "capture", "createdAt", "complete", @@ -11366,14 +10583,6 @@ var LIMIT_KEYS = /* @__PURE__ */ new Set([ ]); var POLICY_KEYS = /* @__PURE__ */ new Set(["schema", "include", "exclude", "hash"]); var CAPTURE_KEYS = /* @__PURE__ */ new Set(["provider", "gitSelection", "physicalSelection", "projection"]); -var CREATION_KEYS = /* @__PURE__ */ new Set([ - "schema", - "protocol", - "policyHash", - "policySnapshotRef", - "policySnapshotHash", - "authorization" -]); var GIT_PROJECTION_KEYS = /* @__PURE__ */ new Set(["provider", "selection"]); var GIT_SELECTION_KEYS = /* @__PURE__ */ new Set([ "schema", @@ -11427,7 +10636,7 @@ var OMISSION_REASONS = /* @__PURE__ */ new Set([ "physical-enumeration-limit", "physical-selection-changed" ]); -var HASH_PATTERN5 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN = /^[a-f0-9]{64}$/u; var GIT_OBJECT_ID_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u; var UNREADABLE_ERROR_CODES = /* @__PURE__ */ new Set(["EACCES", "EPERM"]); var GIT_LIST_STDERR_LIMIT = 64 * 1024; @@ -11487,9 +10696,9 @@ async function terminateNativeProcessTree(child, adapter) { } if (process.platform === "win32") { const configuredSystemRoot = process.env.SystemRoot ?? process.env.WINDIR; - const systemRoot = configuredSystemRoot && path15.win32.isAbsolute(configuredSystemRoot) ? path15.win32.resolve(configuredSystemRoot) : "C:\\Windows"; - const taskkill = path15.win32.join(systemRoot, "System32", "taskkill.exe"); - if (!path15.win32.isAbsolute(taskkill)) { + const systemRoot = configuredSystemRoot && path12.win32.isAbsolute(configuredSystemRoot) ? path12.win32.resolve(configuredSystemRoot) : "C:\\Windows"; + const taskkill = path12.win32.join(systemRoot, "System32", "taskkill.exe"); + if (!path12.win32.isAbsolute(taskkill)) { child.kill("SIGKILL"); child.stdin?.destroy(); child.stdout?.destroy(); @@ -11642,7 +10851,7 @@ function runGitNullRecords(execution, projectRoot, args, options) { options.stdin !== void 0 ); const records = []; - const digest = createHash8("sha256"); + const digest = createHash6("sha256"); const stderr = []; let stderrBytes = 0; let stdoutBytes = 0; @@ -11749,8 +10958,8 @@ async function runGitCheckIgnore(execution, projectRoot, paths) { throw new Error("Native Git check-ignore output exceeded its safety budget"); } return new Set( - result2.records.map((record13) => { - const value = decodeGitRecord(record13); + result2.records.map((record12) => { + const value = decodeGitRecord(record12); return value.endsWith("/") ? value.slice(0, -1) : value; }) ); @@ -11778,7 +10987,7 @@ async function runGitHasOutput(execution, projectRoot, args) { } function safeGitProjectPath(value) { const withoutDirectoryMarker = value.endsWith("/") ? value.slice(0, -1) : value; - if (withoutDirectoryMarker.length === 0 || withoutDirectoryMarker.includes("\\") || path15.posix.isAbsolute(withoutDirectoryMarker) || /^[A-Za-z]:/u.test(withoutDirectoryMarker) || path15.posix.normalize(withoutDirectoryMarker) !== withoutDirectoryMarker || withoutDirectoryMarker === ".." || withoutDirectoryMarker.startsWith("../") || withoutDirectoryMarker.includes("\0")) { + if (withoutDirectoryMarker.length === 0 || withoutDirectoryMarker.includes("\\") || path12.posix.isAbsolute(withoutDirectoryMarker) || /^[A-Za-z]:/u.test(withoutDirectoryMarker) || path12.posix.normalize(withoutDirectoryMarker) !== withoutDirectoryMarker || withoutDirectoryMarker === ".." || withoutDirectoryMarker.startsWith("../") || withoutDirectoryMarker.includes("\0")) { return null; } return withoutDirectoryMarker; @@ -11793,15 +11002,15 @@ function requireSafeGitProjectPaths(values, source) { }); } async function hasGitMetadataBoundary(projectRoot) { - let cursor = path15.resolve(projectRoot); + let cursor = path12.resolve(projectRoot); while (true) { try { - await fs12.lstat(path15.join(cursor, ".git")); + await fs10.lstat(path12.join(cursor, ".git")); return true; } catch (error) { if (error.code !== "ENOENT") throw error; } - const parent = path15.dirname(cursor); + const parent = path12.dirname(cursor); if (parent === cursor) return false; cursor = parent; } @@ -11871,18 +11080,18 @@ async function nativeGitSnapshotSelection(execution, projectRoot, limits = DEFAU const gitlinks = /* @__PURE__ */ new Set(); const addStagedRecords = (records) => { for (const encoded of records) { - const record13 = decodeGitRecord(encoded); - const separator = record13.indexOf(" "); + const record12 = decodeGitRecord(encoded); + const separator = record12.indexOf(" "); if (separator < 0) { throw new Error("Native Git snapshot provider returned a malformed staged record"); } const header = /^(?[0-7]{6}) (?[a-f0-9]{40}|[a-f0-9]{64}) (?[0-3])$/u.exec( - record13.slice(0, separator) + record12.slice(0, separator) )?.groups; if (!header) { throw new Error("Native Git snapshot provider returned a malformed staged header"); } - const relative = safeGitProjectPath(record13.slice(separator + 1)); + const relative = safeGitProjectPath(record12.slice(separator + 1)); if (relative === null) { throw new Error("Native Git snapshot provider returned an unsafe staged path"); } @@ -11986,10 +11195,10 @@ function physicalSelectionRecordType(stat) { return "other"; } async function nativePhysicalSnapshotSelection(options) { - const projectRoot = path15.resolve(options.paths.projectRoot); - const nativeRoot = path15.resolve(options.paths.nativeRoot); - const configFile = path15.resolve(options.paths.configFile); - const selectionFile = path15.join(projectRoot, ".comet", "current-change.json"); + const projectRoot = path12.resolve(options.paths.projectRoot); + const nativeRoot = path12.resolve(options.paths.nativeRoot); + const configFile = path12.resolve(options.paths.configFile); + const selectionFile = path12.join(projectRoot, ".comet", "current-change.json"); const records = []; const omissions = []; const xor = Buffer.alloc(32); @@ -12006,26 +11215,26 @@ async function nativePhysicalSnapshotSelection(options) { stopped = true; return false; }; - const addRecord = (record13) => { - const encoded = Buffer.from(`${record13.type}\0${record13.path}`, "utf8"); - const digest = createHash8("sha256").update("comet.native.physical-selection-record.v1\0").update(encoded).digest(); + const addRecord = (record12) => { + const encoded = Buffer.from(`${record12.type}\0${record12.path}`, "utf8"); + const digest = createHash6("sha256").update("comet.native.physical-selection-record.v1\0").update(encoded).digest(); for (let index = 0; index < xor.length; index += 1) xor[index] ^= digest[index]; sum = sum + BigInt(`0x${digest.toString("hex")}`) & PHYSICAL_SELECTION_SUM_MASK; recordCount += 1; encodedBytes += encoded.byteLength; - if (Buffer.byteLength(record13.path, "utf8") > options.limits.maxPathBytes || encodedBytes > options.limits.maxBytes) { + if (Buffer.byteLength(record12.path, "utf8") > options.limits.maxPathBytes || encodedBytes > options.limits.maxBytes) { overflow = true; stopped = true; return; } - records.push(record13); + records.push(record12); }; const visit = async (directory) => { if (stopped) return; if (!hasExecutionBudget()) return; let handle; try { - handle = await fs12.opendir(directory); + handle = await fs10.opendir(directory); } catch (error) { if (!hasExecutionBudget()) return; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -12067,7 +11276,7 @@ async function nativePhysicalSnapshotSelection(options) { stopped = true; break; } - const target = path15.join(directory, child.name); + const target = path12.join(directory, child.name); const relative = portableRelative(projectRoot, target); if (target === configFile || target === selectionFile || sameOrInside(nativeRoot, target) || options.denylist.some((denied) => sameOrInside(denied, target)) || nativeSensitiveRelativePathReason(relative) !== null) { continue; @@ -12075,7 +11284,7 @@ async function nativePhysicalSnapshotSelection(options) { if (!hasExecutionBudget()) break; let stat; try { - stat = await fs12.lstat(target); + stat = await fs10.lstat(target); } catch (error) { if (!hasExecutionBudget()) break; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -12098,7 +11307,7 @@ async function nativePhysicalSnapshotSelection(options) { if (!hasExecutionBudget()) break; let realDirectory; try { - realDirectory = await fs12.realpath(target); + realDirectory = await fs10.realpath(target); } catch (error) { if (!hasExecutionBudget()) break; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -12250,8 +11459,8 @@ async function inspectGitlinkWorkingTree(execution, target) { } function isSnapshotProjectRef(paths, relative) { if (nativeSensitiveRelativePathReason(relative) !== null) return false; - const target = path15.resolve(paths.projectRoot, ...relative.split("/")); - return !sameOrInside(path15.resolve(paths.nativeRoot), target); + const target = path12.resolve(paths.projectRoot, ...relative.split("/")); + return !sameOrInside(path12.resolve(paths.nativeRoot), target); } function inspectNativeContentSnapshotHealth(value, options = {}) { const manifest = parseNativeContentSnapshotManifest(value); @@ -12270,14 +11479,14 @@ function inspectNativeContentSnapshotHealth(value, options = {}) { }; } function portableRelative(root, target) { - return path15.relative(root, target).split(path15.sep).join("/"); + return path12.relative(root, target).split(path12.sep).join("/"); } function normalizedDenylist(projectRoot, values) { - return values.map((value) => path15.resolve(projectRoot, ...value.split(/[\\/]/u))); + return values.map((value) => path12.resolve(projectRoot, ...value.split(/[\\/]/u))); } function sameOrInside(root, target) { - const normalizedRoot = path15.resolve(root); - const normalizedTarget = path15.resolve(target); + const normalizedRoot = path12.resolve(root); + const normalizedTarget = path12.resolve(target); return normalizedTarget === normalizedRoot || isInsidePath(normalizedRoot, normalizedTarget); } function isUnreadableError(error) { @@ -12448,7 +11657,7 @@ async function sha256FileBounded(file, maxBytes, expected, execution) { if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" }; let handle; try { - handle = await fs12.open(file, "r"); + handle = await fs10.open(file, "r"); } catch (error) { if (!nativeSnapshotExecutionHasBudget(execution)) return { status: "budget-exhausted" }; throw error; @@ -12457,7 +11666,7 @@ async function sha256FileBounded(file, maxBytes, expected, execution) { await handle.close(); return { status: "budget-exhausted" }; } - const hash8 = createHash8("sha256"); + const hash8 = createHash6("sha256"); const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1)); let bytes = 0; try { @@ -12490,7 +11699,7 @@ async function sha256FileBounded(file, maxBytes, expected, execution) { await handle.close(); } } -function record6(value, label) { +function record2(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } @@ -12516,17 +11725,17 @@ function snapshotPath(value, label) { if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0")) { throw new Error(`${label} must be a normalized project-relative path`); } - const normalized = path15.posix.normalize(value); - if (normalized !== value || path15.posix.isAbsolute(value) || normalized === ".." || normalized.startsWith("../")) { + const normalized = path12.posix.normalize(value); + if (normalized !== value || path12.posix.isAbsolute(value) || normalized === ".." || normalized.startsWith("../")) { throw new Error(`${label} must stay inside the project root`); } return value; } function parseEntry(value, index) { - const entry2 = record6(value, `Native snapshot entry ${index}`); + const entry2 = record2(value, `Native snapshot entry ${index}`); rejectUnknown2(entry2, ENTRY_KEYS, `Native snapshot entry ${index}`); const entryPath = snapshotPath(entry2.path, `Native snapshot entry ${index} path`); - if (typeof entry2.hash !== "string" || !HASH_PATTERN5.test(entry2.hash)) { + if (typeof entry2.hash !== "string" || !HASH_PATTERN.test(entry2.hash)) { throw new Error(`Native snapshot entry ${index} hash is invalid`); } if (entry2.type !== "file") throw new Error(`Native snapshot entry ${index} type is invalid`); @@ -12538,7 +11747,7 @@ function parseEntry(value, index) { }; } function parseOmission(value, index) { - const omission = record6(value, `Native snapshot omission ${index}`); + const omission = record2(value, `Native snapshot omission ${index}`); rejectUnknown2(omission, OMISSION_KEYS, `Native snapshot omission ${index}`); if (!OMISSION_TYPES.has(omission.type)) { throw new Error(`Native snapshot omission ${index} type is invalid`); @@ -12554,9 +11763,9 @@ function parseOmission(value, index) { }; } function parseOmissionOverflow(value) { - const overflow = record6(value, "Native snapshot omission overflow"); + const overflow = record2(value, "Native snapshot omission overflow"); rejectUnknown2(overflow, OMISSION_OVERFLOW_KEYS, "Native snapshot omission overflow"); - if (typeof overflow.hash !== "string" || !HASH_PATTERN5.test(overflow.hash)) { + if (typeof overflow.hash !== "string" || !HASH_PATTERN.test(overflow.hash)) { throw new Error("Native snapshot omission overflow hash is invalid"); } const expectedRef = `native-snapshot://omitted-overflow/${overflow.hash}`; @@ -12570,9 +11779,9 @@ function parseOmissionOverflow(value) { }; } function parseGitSelectionStreamEvidence(value, label) { - const stream = record6(value, label); + const stream = record2(value, label); rejectUnknown2(stream, GIT_SELECTION_STREAM_KEYS, label); - if (typeof stream.hash !== "string" || !HASH_PATTERN5.test(stream.hash)) { + if (typeof stream.hash !== "string" || !HASH_PATTERN.test(stream.hash)) { throw new Error(`${label} hash is invalid`); } if (typeof stream.overflow !== "boolean") { @@ -12596,7 +11805,7 @@ function parseGitSelectionStreamEvidence(value, label) { }; } function parseGitSelectionEvidence(value) { - const selection = record6(value, "Native Git selection evidence"); + const selection = record2(value, "Native Git selection evidence"); rejectUnknown2(selection, GIT_SELECTION_KEYS, "Native Git selection evidence"); if (selection.schema !== "comet.native.git-selection.v1") { throw new Error("Native Git selection evidence schema is invalid"); @@ -12656,9 +11865,9 @@ function parseGitSelectionEvidence(value) { }; } function parsePhysicalSelectionStreamEvidence(value, label) { - const stream = record6(value, label); + const stream = record2(value, label); rejectUnknown2(stream, PHYSICAL_SELECTION_STREAM_KEYS, label); - if (typeof stream.hash !== "string" || !HASH_PATTERN5.test(stream.hash)) { + if (typeof stream.hash !== "string" || !HASH_PATTERN.test(stream.hash)) { throw new Error(`${label} hash is invalid`); } if (typeof stream.overflow !== "boolean" || typeof stream.unstable !== "boolean") { @@ -12685,7 +11894,7 @@ function parsePhysicalSelectionStreamEvidence(value, label) { }; } function parsePhysicalSelectionEvidence(value) { - const selection = record6(value, "Native physical selection evidence"); + const selection = record2(value, "Native physical selection evidence"); rejectUnknown2(selection, PHYSICAL_SELECTION_KEYS, "Native physical selection evidence"); if (selection.schema !== "comet.native.physical-selection.v1") { throw new Error("Native physical selection evidence schema is invalid"); @@ -12718,7 +11927,7 @@ function parsePhysicalSelectionEvidence(value) { }; } function parseNativeContentSnapshotManifest(value) { - const manifest = record6(value, "Native content snapshot manifest"); + const manifest = record2(value, "Native content snapshot manifest"); rejectUnknown2(manifest, MANIFEST_KEYS, "Native content snapshot manifest"); if (manifest.schema !== "comet.native.content-snapshot.v1") { throw new Error("Unsupported Native content snapshot schema"); @@ -12726,28 +11935,9 @@ function parseNativeContentSnapshotManifest(value) { if (!SNAPSHOT_ORIGINS.has(manifest.origin)) { throw new Error("Native content snapshot origin is invalid"); } - let creation; - if (manifest.creation !== void 0) { - const value2 = record6(manifest.creation, "Native change creation binding"); - rejectUnknown2(value2, CREATION_KEYS, "Native change creation binding"); - if (value2.schema !== "comet.native.change-creation-binding.v1" || value2.protocol !== "signed-v2" || typeof value2.policyHash !== "string" || !HASH_PATTERN5.test(value2.policyHash) || typeof value2.policySnapshotHash !== "string" || !HASH_PATTERN5.test(value2.policySnapshotHash) || typeof value2.policySnapshotRef !== "string" || !/^runtime\/trust\/review-policy-[a-f0-9]{64}\.json$/u.test(value2.policySnapshotRef)) { - throw new Error("Native change creation binding is invalid"); - } - creation = { - schema: "comet.native.change-creation-binding.v1", - protocol: "signed-v2", - policyHash: value2.policyHash, - policySnapshotRef: value2.policySnapshotRef, - policySnapshotHash: value2.policySnapshotHash, - authorization: parseNativeCreationAuthorization(value2.authorization) - }; - } - if (manifest.origin !== "change-created" && creation !== void 0) { - throw new Error("Native change creation binding does not match snapshot origin"); - } let capture; if (manifest.capture !== void 0) { - const captureValue = record6(manifest.capture, "Native content snapshot capture"); + const captureValue = record2(manifest.capture, "Native content snapshot capture"); rejectUnknown2(captureValue, CAPTURE_KEYS, "Native content snapshot capture"); if (captureValue.provider !== "git" && captureValue.provider !== "physical-tree") { throw new Error("Native content snapshot capture provider is invalid"); @@ -12756,7 +11946,7 @@ function parseNativeContentSnapshotManifest(value) { const physicalSelection2 = captureValue.physicalSelection === void 0 ? void 0 : parsePhysicalSelectionEvidence(captureValue.physicalSelection); let projection = null; if (captureValue.projection !== void 0) { - const projectionValue = record6(captureValue.projection, "Native content snapshot projection"); + const projectionValue = record2(captureValue.projection, "Native content snapshot projection"); rejectUnknown2(projectionValue, GIT_PROJECTION_KEYS, "Native content snapshot projection"); if (projectionValue.provider !== "git") { throw new Error("Native content snapshot projection provider is invalid"); @@ -12793,7 +11983,7 @@ function parseNativeContentSnapshotManifest(value) { if (typeof manifest.complete !== "boolean") { throw new Error("Native content snapshot complete flag is invalid"); } - const limitValue = record6(manifest.limits, "Native content snapshot limits"); + const limitValue = record2(manifest.limits, "Native content snapshot limits"); rejectUnknown2(limitValue, LIMIT_KEYS, "Native content snapshot limits"); const limits = { maxFiles: positiveInteger(limitValue.maxFiles, "Native snapshot maxFiles"), @@ -12809,7 +11999,7 @@ function parseNativeContentSnapshotManifest(value) { }; let policy; if (manifest.policy !== void 0) { - const policyValue = record6(manifest.policy, "Native snapshot policy"); + const policyValue = record2(manifest.policy, "Native snapshot policy"); rejectUnknown2(policyValue, POLICY_KEYS, "Native snapshot policy"); if (policyValue.schema !== "comet.native.snapshot-policy.v1") { throw new Error("Native snapshot policy schema is invalid"); @@ -12902,7 +12092,6 @@ function parseNativeContentSnapshotManifest(value) { const parsed = { schema: "comet.native.content-snapshot.v1", origin: manifest.origin, - ...creation ? { creation } : {}, ...capture ? { capture } : {}, createdAt: manifest.createdAt, complete: manifest.complete, @@ -12923,7 +12112,7 @@ async function filterNativeContentSnapshotToProjectScope(paths, value, options = if (manifest.capture?.provider === "git" || manifest.capture?.projection || manifest.capture?.physicalSelection) { return manifest; } - const projectRoot = path15.resolve(paths.projectRoot); + const projectRoot = path12.resolve(paths.projectRoot); const execution = createNativeSnapshotExecution(options); const gitSelectionLimits = resolveNativeGitSelectionLimits(options.gitSelectionLimits); const selection = await nativeGitSnapshotSelection( @@ -13042,10 +12231,10 @@ async function filterNativeContentSnapshotToProjectScope(paths, value, options = return parseNativeContentSnapshotManifest(projected); } function nativeBaselineManifestFile(paths, name) { - if (!CHANGE_NAME_PATTERN3.test(name)) throw new Error(`Invalid Native change name: ${name}`); - const changeDir = path15.join(paths.changesDir, name); + if (!CHANGE_NAME_PATTERN.test(name)) throw new Error(`Invalid Native change name: ${name}`); + const changeDir = path12.join(paths.changesDir, name); if (!isInsidePath(paths.changesDir, changeDir)) throw new Error("Native change path escaped"); - return path15.join(changeDir, "runtime", "baseline-manifest.json"); + return path12.join(changeDir, "runtime", "baseline-manifest.json"); } async function createNativeContentSnapshot(paths, options = {}) { const limits = { @@ -13067,12 +12256,12 @@ async function createNativeContentSnapshot(paths, options = {}) { if (limits.maxFiles < 1 || limits.maxFileBytes < 1 || limits.maxTotalBytes < 1 || limits.maxManifestBytes < 1) { throw new Error("Native snapshot limits must be positive"); } - const projectRoot = path15.resolve(paths.projectRoot); - const physicalProjectRoot = await fs12.realpath(projectRoot); - const nativeRoot = path15.resolve(paths.nativeRoot); - const physicalNativeRoot = await fs12.realpath(nativeRoot); - const configFile = path15.resolve(paths.configFile); - const selectionFile = path15.join(projectRoot, ".comet", "current-change.json"); + const projectRoot = path12.resolve(paths.projectRoot); + const physicalProjectRoot = await fs10.realpath(projectRoot); + const nativeRoot = path12.resolve(paths.nativeRoot); + const physicalNativeRoot = await fs10.realpath(nativeRoot); + const configFile = path12.resolve(paths.configFile); + const selectionFile = path12.join(projectRoot, ".comet", "current-change.json"); const denylist = normalizedDenylist(projectRoot, options.denylist ?? []); const entries = []; const omitted = []; @@ -13142,7 +12331,7 @@ async function createNativeContentSnapshot(paths, options = {}) { if (!nativeSnapshotExecutionHasBudget(execution)) return; let realTarget; try { - realTarget = await fs12.realpath(target); + realTarget = await fs10.realpath(target); } catch (error) { if (!nativeSnapshotExecutionHasBudget(execution)) return; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -13183,9 +12372,9 @@ async function createNativeContentSnapshot(paths, options = {}) { return; } if (!nativeSnapshotExecutionHasBudget(execution)) return; - afterRealTarget = await fs12.realpath(target); + afterRealTarget = await fs10.realpath(target); if (!nativeSnapshotExecutionHasBudget(execution)) return; - after = await fs12.lstat(target); + after = await fs10.lstat(target); } catch (error) { if (!nativeSnapshotExecutionHasBudget(execution)) return; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -13219,11 +12408,11 @@ async function createNativeContentSnapshot(paths, options = {}) { let secondTarget; let after; try { - firstTarget = await fs12.readlink(target, { encoding: "buffer" }); + firstTarget = await fs10.readlink(target, { encoding: "buffer" }); if (!nativeSnapshotExecutionHasBudget(execution)) return; - after = await fs12.lstat(target); + after = await fs10.lstat(target); if (!nativeSnapshotExecutionHasBudget(execution)) return; - secondTarget = await fs12.readlink(target, { encoding: "buffer" }); + secondTarget = await fs10.readlink(target, { encoding: "buffer" }); } catch (error) { if (!nativeSnapshotExecutionHasBudget(execution)) return; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -13253,7 +12442,7 @@ async function createNativeContentSnapshot(paths, options = {}) { omit({ path: relative, size, type: "other", reason: "total-size" }); return; } - const hash8 = createHash8("sha256").update("symlink\0").update(firstTarget).digest("hex"); + const hash8 = createHash6("sha256").update("symlink\0").update(firstTarget).digest("hex"); if (!nativeSnapshotExecutionHasBudget(execution)) return; await recordCapturedEntry( { path: relative, hash: hash8, size, type: "file" }, @@ -13263,10 +12452,10 @@ async function createNativeContentSnapshot(paths, options = {}) { const captureForcedProtectedRefs = async () => { for (const relative of [NATIVE_REVIEW_TRUST_POLICY_REF]) { if (capturedEntryValidations.has(relative)) continue; - const target = path15.resolve(projectRoot, ...relative.split("/")); + const target = path12.resolve(projectRoot, ...relative.split("/")); let stat; try { - stat = await fs12.lstat(target); + stat = await fs10.lstat(target); } catch (error) { if (error.code === "ENOENT") continue; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -13299,9 +12488,9 @@ async function createNativeContentSnapshot(paths, options = {}) { let realTarget2; let stat2; try { - realTarget2 = await fs12.realpath(validation2.target); + realTarget2 = await fs10.realpath(validation2.target); if (!nativeSnapshotExecutionHasBudget(execution)) return; - stat2 = await fs12.lstat(validation2.target); + stat2 = await fs10.lstat(validation2.target); } catch (error) { if (!nativeSnapshotExecutionHasBudget(execution)) return; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -13328,9 +12517,9 @@ async function createNativeContentSnapshot(paths, options = {}) { let stat2; let rawTarget; try { - stat2 = await fs12.lstat(validation2.target); + stat2 = await fs10.lstat(validation2.target); if (!nativeSnapshotExecutionHasBudget(execution)) return; - rawTarget = await fs12.readlink(validation2.target, { encoding: "buffer" }); + rawTarget = await fs10.readlink(validation2.target, { encoding: "buffer" }); } catch (error) { if (!nativeSnapshotExecutionHasBudget(execution)) return; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; @@ -13356,9 +12545,9 @@ async function createNativeContentSnapshot(paths, options = {}) { let realTarget; let stat; try { - realTarget = await fs12.realpath(validation2.target); + realTarget = await fs10.realpath(validation2.target); if (!nativeSnapshotExecutionHasBudget(execution)) return; - stat = await fs12.lstat(validation2.target); + stat = await fs10.lstat(validation2.target); } catch { if (!nativeSnapshotExecutionHasBudget(execution)) return; invalidateCapturedEntry(relative, { @@ -13414,7 +12603,7 @@ async function createNativeContentSnapshot(paths, options = {}) { for (const [relative, target] of capturedTrackedAbsences) { if (!nativeSnapshotExecutionHasBudget(execution)) return; try { - const stat = await fs12.lstat(target); + const stat = await fs10.lstat(target); if (!nativeSnapshotExecutionHasBudget(execution)) return; omit({ path: relative, @@ -13448,21 +12637,21 @@ async function createNativeContentSnapshot(paths, options = {}) { hooks: options.physicalSelectionHooks }); await options.physicalSelectionHooks?.afterInitialSelection?.(); - for (const record13 of before.records) { - if (record13.type !== "file" && record13.type !== "symlink") continue; - if (capturedEntryValidations.has(record13.path)) continue; + for (const record12 of before.records) { + if (record12.type !== "file" && record12.type !== "symlink") continue; + if (capturedEntryValidations.has(record12.path)) continue; if (remainingNativeSnapshotTime(execution) < 1) break; - if (!snapshotPolicyIncludes(policy, record13.path, execution)) continue; + if (!snapshotPolicyIncludes(policy, record12.path, execution)) continue; if (remainingNativeSnapshotTime(execution) < 1) break; - const target = path15.resolve(projectRoot, ...record13.path.split("/")); + const target = path12.resolve(projectRoot, ...record12.path.split("/")); let stat; try { - stat = await fs12.lstat(target); + stat = await fs10.lstat(target); } catch (error) { if (!nativeSnapshotExecutionHasBudget(execution)) break; if (!isUnreadableError(error) && !isChangedDuringReadError(error)) throw error; omit({ - path: record13.path, + path: record12.path, size: null, type: "file", reason: isChangedDuringReadError(error) ? "changed-during-read" : "unreadable" @@ -13470,21 +12659,21 @@ async function createNativeContentSnapshot(paths, options = {}) { continue; } if (remainingNativeSnapshotTime(execution) < 1) break; - if (record13.type === "symlink" && stat.isSymbolicLink()) { - await captureSymbolicLink(target, record13.path, stat); + if (record12.type === "symlink" && stat.isSymbolicLink()) { + await captureSymbolicLink(target, record12.path, stat); if (remainingNativeSnapshotTime(execution) < 1) break; continue; } - if (record13.type !== "file" || !stat.isFile() || stat.isSymbolicLink()) { + if (record12.type !== "file" || !stat.isFile() || stat.isSymbolicLink()) { omit({ - path: record13.path, + path: record12.path, size: stat.isFile() ? stat.size : null, type: stat.isDirectory() ? "directory" : stat.isFile() ? "file" : "other", reason: "changed-during-read" }); continue; } - await captureFile(target, record13.path, stat); + await captureFile(target, record12.path, stat); if (remainingNativeSnapshotTime(execution) < 1) break; } if (remainingNativeSnapshotTime(execution) >= 1) await revalidateCapturedEntries(); @@ -13514,7 +12703,7 @@ async function createNativeContentSnapshot(paths, options = {}) { if (remainingNativeSnapshotTime(execution) < 1) break; if (!snapshotPolicyIncludes(policy, relative, execution)) continue; if (remainingNativeSnapshotTime(execution) < 1) break; - const target = path15.resolve(projectRoot, ...relative.split("/")); + const target = path12.resolve(projectRoot, ...relative.split("/")); if (target === configFile || target === selectionFile || denylist.some((denied) => sameOrInside(denied, target))) { continue; } @@ -13526,7 +12715,7 @@ async function createNativeContentSnapshot(paths, options = {}) { let realGitlink; let gitlinkStat; try { - [realGitlink, gitlinkStat] = await Promise.all([fs12.realpath(target), fs12.lstat(target)]); + [realGitlink, gitlinkStat] = await Promise.all([fs10.realpath(target), fs10.lstat(target)]); } catch { omit({ path: relative, @@ -13584,7 +12773,7 @@ async function createNativeContentSnapshot(paths, options = {}) { } let before; try { - before = await fs12.lstat(target); + before = await fs10.lstat(target); } catch (error) { if (isChangedDuringReadError(error) && gitSelection.tracked.has(relative)) { capturedTrackedAbsences.set(relative, target); @@ -13713,11 +12902,11 @@ async function readNativeBaselineManifest(paths, name) { } // domains/comet-native/native-trajectory-recovery.ts -import { createHash as createHash10 } from "crypto"; -import path19 from "path"; +import { createHash as createHash8 } from "crypto"; +import path16 from "path"; // domains/engine/storage-layout.ts -import path16 from "path"; +import path13 from "path"; var NATIVE_RUN_STORAGE = /* @__PURE__ */ Object.freeze({ stateRef: "runtime/run-state.json", pendingRef: "runtime/pending-action.json", @@ -13728,7 +12917,7 @@ var NATIVE_RUN_STORAGE = /* @__PURE__ */ Object.freeze({ snapshotsRef: "runtime/skill-snapshots" }); function assertRunStorageRef(value) { - if (value.length === 0 || path16.isAbsolute(value) || /^(?:[A-Za-z]:|[\\/]|~)/u.test(value) || value.split(/[\\/]/u).includes("..")) { + if (value.length === 0 || path13.isAbsolute(value) || /^(?:[A-Za-z]:|[\\/]|~)/u.test(value) || value.split(/[\\/]/u).includes("..")) { throw new Error("Run storage ref must stay inside the Run root"); } } @@ -13737,13 +12926,13 @@ function assertRunStorageLayout(storage) { } // domains/comet-native/native-run-store.ts -import { createHash as createHash9 } from "node:crypto"; -import { constants as fsConstants4, promises as fs13 } from "node:fs"; -import path18 from "node:path"; +import { createHash as createHash7 } from "node:crypto"; +import { constants as fsConstants3, promises as fs11 } from "node:fs"; +import path15 from "node:path"; import { TextDecoder as TextDecoder4 } from "node:util"; // domains/engine/state.ts -import path17 from "path"; +import path14 from "path"; var field = (doc, key) => { const value = doc[key]; return value === null || value === void 0 ? null : String(value); @@ -13757,7 +12946,7 @@ function requiredString(doc, key) { } function requiredRunReference(doc, key) { const value = requiredString(doc, key); - if (path17.isAbsolute(value) || /^(?:[A-Za-z]:|[\\/]|~)/u.test(value) || value.split(/[\\/]/u).includes("..")) { + if (path14.isAbsolute(value) || /^(?:[A-Za-z]:|[\\/]|~)/u.test(value) || value.split(/[\\/]/u).includes("..")) { throw new Error(`Invalid Run state: ${key} must stay inside the change directory`); } return value; @@ -13913,9 +13102,9 @@ var NATIVE_RUN_IO_LIMITS = { contextBytes: 1024 * 1024, artifactsBytes: 1024 * 1024 }; -function isInside6(parent, target) { - const relative = path18.relative(parent, target); - return relative === "" || !path18.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path18.sep}`); +function isInside5(parent, target) { + const relative = path15.relative(parent, target); + return relative === "" || !path15.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path15.sep}`); } function asIdentity(stat) { return { @@ -13950,34 +13139,34 @@ function runFile(changeDir, kind, relativePath) { if (relativePath !== void 0 && relativePath !== expected) { throw new Error(`Native Run ${kind} ref must be ${expected}`); } - const root = path18.resolve(changeDir); - const target = path18.resolve(root, ...expected.split("/")); - if (!isInside6(root, target)) throw new Error("Native Run path must stay inside its change"); + const root = path15.resolve(changeDir); + const target = path15.resolve(root, ...expected.split("/")); + if (!isInside5(root, target)) throw new Error("Native Run path must stay inside its change"); return target; } async function directoryIdentity2(directory) { - const stat = await fs13.lstat(directory); + const stat = await fs11.lstat(directory); if (!stat.isDirectory() || stat.isSymbolicLink()) { throw new Error(`Native Run parent must be a real directory: ${directory}`); } return { path: directory, - realPath: await fs13.realpath(directory), + realPath: await fs11.realpath(directory), dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs }; } async function captureDirectoryChain3(root, directory) { - const lexicalRoot = path18.resolve(root); - const lexicalDirectory = path18.resolve(directory); - if (!isInside6(lexicalRoot, lexicalDirectory)) { + const lexicalRoot = path15.resolve(root); + const lexicalDirectory = path15.resolve(directory); + if (!isInside5(lexicalRoot, lexicalDirectory)) { throw new Error("Native Run path is outside its change"); } const chain = [await directoryIdentity2(lexicalRoot)]; let cursor = lexicalRoot; - for (const segment of path18.relative(lexicalRoot, lexicalDirectory).split(path18.sep).filter(Boolean)) { - cursor = path18.join(cursor, segment); + for (const segment of path15.relative(lexicalRoot, lexicalDirectory).split(path15.sep).filter(Boolean)) { + cursor = path15.join(cursor, segment); let identity; try { identity = await directoryIdentity2(cursor); @@ -13985,7 +13174,7 @@ async function captureDirectoryChain3(root, directory) { if (error.code === "ENOENT") return null; throw error; } - if (!isInside6(chain[0].realPath, identity.realPath)) { + if (!isInside5(chain[0].realPath, identity.realPath)) { throw new Error(`Native Run parent resolves outside its change: ${cursor}`); } chain.push(identity); @@ -13994,8 +13183,8 @@ async function captureDirectoryChain3(root, directory) { } async function verifyDirectoryChain4(chain) { for (const identity of chain) { - const stat = await fs13.lstat(identity.path); - if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity4(identity, stat) || await fs13.realpath(identity.path) !== identity.realPath) { + const stat = await fs11.lstat(identity.path); + if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity4(identity, stat) || await fs11.realpath(identity.path) !== identity.realPath) { throw new Error(`Native Run parent changed during I/O: ${identity.path}`); } } @@ -14015,12 +13204,12 @@ async function readHandleBounded2(handle, maxBytes, label) { return Buffer.concat(chunks, total); } async function readProtectedText(changeDir, file, maxBytes, label, hooks) { - const chain = await captureDirectoryChain3(changeDir, path18.dirname(file)); + const chain = await captureDirectoryChain3(changeDir, path15.dirname(file)); if (!chain) return null; await hooks?.afterParentChainCaptured?.(); let before; try { - before = await fs13.lstat(file); + before = await fs11.lstat(file); } catch (error) { if (error.code === "ENOENT") return null; throw error; @@ -14030,22 +13219,22 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) { } if (before.size > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`); const beforeIdentity = asIdentity(before); - const beforeRealPath = await fs13.realpath(file); - if (!isInside6(chain[0].realPath, beforeRealPath)) { + const beforeRealPath = await fs11.realpath(file); + if (!isInside5(chain[0].realPath, beforeRealPath)) { throw new Error(`${label} resolves outside its change`); } - const flags = process.platform === "win32" ? fsConstants4.O_RDONLY : fsConstants4.O_RDONLY | fsConstants4.O_NOFOLLOW | fsConstants4.O_NONBLOCK; + const flags = process.platform === "win32" ? fsConstants3.O_RDONLY : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK; let handle; try { - handle = await fs13.open(file, flags); + handle = await fs11.open(file, flags); } catch (error) { throw new Error(`${label} changed while opening`, { cause: error }); } try { const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([ handle.stat(), - fs13.lstat(file), - fs13.realpath(file) + fs11.lstat(file), + fs11.realpath(file) ]); await verifyDirectoryChain4(chain); if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== beforeRealPath || !sameFileIdentity5(beforeIdentity, opened) || !sameFileIdentity5(beforeIdentity, pathAfterOpen)) { @@ -14056,8 +13245,8 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) { await hooks?.beforeFinalCheck?.(); const [afterHandle, afterPath, afterRealPath] = await Promise.all([ handle.stat(), - fs13.lstat(file), - fs13.realpath(file) + fs11.lstat(file), + fs11.realpath(file) ]); await verifyDirectoryChain4(chain); if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== beforeRealPath || !sameFileIdentity5(beforeIdentity, afterHandle) || !sameFileIdentity5(beforeIdentity, afterPath)) { @@ -14078,36 +13267,36 @@ async function readProtectedText(changeDir, file, maxBytes, label, hooks) { } } async function captureTarget(changeDir, file, label) { - const chain = await captureDirectoryChain3(changeDir, path18.dirname(file)); + const chain = await captureDirectoryChain3(changeDir, path15.dirname(file)); if (!chain) return { exists: false }; let stat; try { - stat = await fs13.lstat(file); + stat = await fs11.lstat(file); } catch (error) { if (error.code === "ENOENT") return { exists: false }; throw error; } if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a regular file`); - const realPath = await fs13.realpath(file); - if (!isInside6(chain[0].realPath, realPath)) + const realPath = await fs11.realpath(file); + if (!isInside5(chain[0].realPath, realPath)) throw new Error(`${label} resolves outside its change`); await verifyDirectoryChain4(chain); return { exists: true, identity: asIdentity(stat), realPath }; } async function verifyTarget(changeDir, file, expected, label) { - const chain = await captureDirectoryChain3(changeDir, path18.dirname(file)); + const chain = await captureDirectoryChain3(changeDir, path15.dirname(file)); if (!chain) { if (!expected.exists) return; throw new Error(`${label} parent changed before commit`); } let stat; try { - stat = await fs13.lstat(file); + stat = await fs11.lstat(file); } catch (error) { if (error.code === "ENOENT" && !expected.exists) return; throw new Error(`${label} changed before commit`, { cause: error }); } - if (!expected.exists || !stat.isFile() || stat.isSymbolicLink() || !sameFileIdentity5(expected.identity, stat) || await fs13.realpath(file) !== expected.realPath) { + if (!expected.exists || !stat.isFile() || stat.isSymbolicLink() || !sameFileIdentity5(expected.identity, stat) || await fs11.realpath(file) !== expected.realPath) { throw new Error(`${label} changed before commit`); } await verifyDirectoryChain4(chain); @@ -14223,7 +13412,7 @@ async function replaceNativeTrajectoryText(changeDir, relativePath, content, exp "Native Run trajectory" ); if (!existing) throw new Error("Native Run trajectory disappeared before repair"); - const actualHash = createHash9("sha256").update(existing.text, "utf8").digest("hex"); + const actualHash = createHash7("sha256").update(existing.text, "utf8").digest("hex"); if (actualHash !== expectedHash) { throw new Error("Native Run trajectory changed before repair"); } @@ -14290,7 +13479,7 @@ async function writeNativeCheckpoint(changeDir, relativePath, checkpoint, hooks) } // domains/comet-native/native-trajectory-recovery.ts -var CHANGE_NAME_PATTERN4 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; +var CHANGE_NAME_PATTERN2 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var NativeTrajectoryRepairRequiredError = class extends Error { constructor(inspection) { super( @@ -14303,13 +13492,13 @@ var NativeTrajectoryRepairRequiredError = class extends Error { code = "native-trajectory-tail-repair-required"; }; function sha256Buffer(value) { - return createHash10("sha256").update(value).digest("hex"); + return createHash8("sha256").update(value).digest("hex"); } function trajectoryFile(paths, name) { - if (!CHANGE_NAME_PATTERN4.test(name)) throw new Error(`Invalid Native change name: ${name}`); - const changeDir = path19.join(paths.changesDir, name); + if (!CHANGE_NAME_PATTERN2.test(name)) throw new Error(`Invalid Native change name: ${name}`); + const changeDir = path16.join(paths.changesDir, name); if (!isInsidePath(paths.changesDir, changeDir)) throw new Error("Native change path escaped"); - return path19.join(changeDir, "runtime", "trajectory.jsonl"); + return path16.join(changeDir, "runtime", "trajectory.jsonl"); } function parseCompleteLines(content) { const lines = content.split(/\n/u); @@ -14370,7 +13559,7 @@ function analyzeTrajectory(file, source) { async function inspectFile(paths, name) { const file = trajectoryFile(paths, name); await resolveContainedNativePath(paths.nativeRoot, file); - const changeDir = path19.join(paths.changesDir, name); + const changeDir = path16.join(paths.changesDir, name); const content = await readNativeTrajectoryText(changeDir, NATIVE_RUN_STORAGE.trajectoryRef); return content === null ? { status: "clean", file } : analyzeTrajectory(file, Buffer.from(content)); } @@ -14391,7 +13580,7 @@ async function repairNativeTrajectoryTail(paths, name, hooks) { } try { await replaceNativeTrajectoryText( - path19.join(paths.changesDir, name), + path16.join(paths.changesDir, name), NATIVE_RUN_STORAGE.trajectoryRef, result2.targetContent, result2.inspection.originalHash, @@ -14415,10 +13604,10 @@ async function repairNativeTrajectoryTail(paths, name, hooks) { } // domains/comet-native/native-workspace.ts -import { createHash as createHash11 } from "node:crypto"; -import { promises as fs14 } from "node:fs"; -import path20 from "node:path"; -var HASH_PATTERN6 = /^[a-f0-9]{64}$/u; +import { createHash as createHash9 } from "node:crypto"; +import { promises as fs12 } from "node:fs"; +import path17 from "node:path"; +var HASH_PATTERN2 = /^[a-f0-9]{64}$/u; var MAX_WORKSPACE_IDENTITY_BYTES = 16 * 1024; var NATIVE_WORKSPACE_ADVISORY_CODES = /* @__PURE__ */ new Set([ "workspace-root-changed", @@ -14428,8 +13617,8 @@ function isNativeWorkspaceAdvisoryCode(code) { return NATIVE_WORKSPACE_ADVISORY_CODES.has(code); } function portableRelative2(parent, target) { - const relative = path20.relative(parent, target); - if (path20.isAbsolute(relative) || relative === ".." || relative.startsWith(`..${path20.sep}`)) { + const relative = path17.relative(parent, target); + if (path17.isAbsolute(relative) || relative === ".." || relative.startsWith(`..${path17.sep}`)) { return null; } return relative.replaceAll("\\", "/") || "."; @@ -14441,38 +13630,38 @@ function hasControlCharacter(value) { }); } function normalizedPortableRef(value, label) { - if (value.length === 0 || hasControlCharacter(value) || value.includes("\\") || path20.posix.isAbsolute(value) || /^(?:[A-Za-z]:|~)/u.test(value) || value.split("/").includes("..")) { + if (value.length === 0 || hasControlCharacter(value) || value.includes("\\") || path17.posix.isAbsolute(value) || /^(?:[A-Za-z]:|~)/u.test(value) || value.split("/").includes("..")) { throw new Error(`${label} must be a portable project-relative path`); } - const normalized = path20.posix.normalize(value); + const normalized = path17.posix.normalize(value); if (normalized !== value || normalized === ".." || normalized.startsWith("../")) { throw new Error(`${label} must be a normalized project-relative path`); } return normalized; } function identityHash(tag, value) { - return createHash11("sha256").update(`${tag} + return createHash9("sha256").update(`${tag} ${value}`).digest("hex"); } async function physicalDirectoryIdentity(tag, value) { - const realPath = await fs14.realpath(value); - const stat = await fs14.lstat(realPath); + const realPath = await fs12.realpath(value); + const stat = await fs12.lstat(realPath); if (!stat.isDirectory() || stat.isSymbolicLink()) { throw new Error("Native workspace identity requires a real directory"); } - const normalizedPath = process.platform === "win32" ? path20.normalize(realPath).toLowerCase() : realPath; + const normalizedPath = process.platform === "win32" ? path17.normalize(realPath).toLowerCase() : realPath; return identityHash(tag, `${normalizedPath} ${stat.dev} ${stat.ino} ${stat.birthtimeMs}`); } async function directoryPathIdentity(tag, value) { - const realPath = await fs14.realpath(value); - const stat = await fs14.lstat(realPath); + const realPath = await fs12.realpath(value); + const stat = await fs12.lstat(realPath); if (!stat.isDirectory() || stat.isSymbolicLink()) { throw new Error("Native workspace identity requires a real directory"); } - const normalizedPath = process.platform === "win32" ? path20.normalize(realPath).toLowerCase() : realPath; + const normalizedPath = process.platform === "win32" ? path17.normalize(realPath).toLowerCase() : realPath; return identityHash(tag, normalizedPath); } function isoTimestamp(value) { @@ -14506,7 +13695,7 @@ function assertIdentity(value) { if (root.schema !== "comet.native.workspace.v2") { throw new Error("Unsupported Native workspace identity"); } - if (!Number.isSafeInteger(root.capturedRevision) || root.capturedRevision < 1 || typeof root.nativeRootRef !== "string" || !HASH_PATTERN6.test(String(root.projectRootId)) || !HASH_PATTERN6.test(String(root.nativeRootId))) { + if (!Number.isSafeInteger(root.capturedRevision) || root.capturedRevision < 1 || typeof root.nativeRootRef !== "string" || !HASH_PATTERN2.test(String(root.projectRootId)) || !HASH_PATTERN2.test(String(root.nativeRootId))) { throw new Error("Native workspace identity is invalid"); } isoTimestamp(root.capturedAt); @@ -14516,15 +13705,15 @@ function assertIdentity(value) { if (hasProjectPathId !== hasNativePathId) { throw new Error("Native workspace path identities must be provided together"); } - if (hasProjectPathId && !HASH_PATTERN6.test(String(root.projectRootPathId)) || hasNativePathId && !HASH_PATTERN6.test(String(root.nativeRootPathId))) { + if (hasProjectPathId && !HASH_PATTERN2.test(String(root.projectRootPathId)) || hasNativePathId && !HASH_PATTERN2.test(String(root.nativeRootPathId))) { throw new Error("Native workspace path identity is invalid"); } - if (root.sessionHash !== void 0 && !HASH_PATTERN6.test(String(root.sessionHash))) { + if (root.sessionHash !== void 0 && !HASH_PATTERN2.test(String(root.sessionHash))) { throw new Error("Native workspace session hash is invalid"); } } function nativeWorkspaceFile(paths, name) { - return path20.join(paths.changesDir, name, "runtime", "workspace.json"); + return path17.join(paths.changesDir, name, "runtime", "workspace.json"); } function nativeWorkspaceRef(paths, name) { const relative = portableRelative2(paths.nativeRoot, nativeWorkspaceFile(paths, name)); @@ -14691,7 +13880,7 @@ var PHASES = /* @__PURE__ */ new Set(["shape", "build", "verify", "archive"]); var APPROVALS = /* @__PURE__ */ new Set(["implicit", "confirmed"]); var VERIFY_RESULTS = /* @__PURE__ */ new Set(["pending", "pass", "fail"]); var NATIVE_CHANGE_STATE_FILE = "comet-state.yaml"; -var HASH_PATTERN7 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN3 = /^[a-f0-9]{64}$/u; var NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var CONTENT_ADDRESSED_REF_PATTERN = /^runtime\/evidence\/(scopes|allowances|verifications)\/([a-f0-9]{64})\.json$/u; var NativeSchemaMigrationRequiredError = class extends Error { @@ -14776,7 +13965,7 @@ var NATIVE_BRIEF_TEMPLATE = [ "# Verification expectations", "" ].join("\n"); -function record7(value, label) { +function record3(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be a mapping`); } @@ -14793,12 +13982,12 @@ function assertCapabilityId(value) { if (!NAME_PATTERN.test(value)) throw new Error(`Invalid Native capability id: ${value}`); } function assertRelativeRef(value, label) { - if (value.length === 0 || path21.isAbsolute(value) || /^(?:[A-Za-z]:|~|[\\/])/u.test(value) || value.split(/[\\/]/u).includes("..")) { + if (value.length === 0 || path18.isAbsolute(value) || /^(?:[A-Za-z]:|~|[\\/])/u.test(value) || value.split(/[\\/]/u).includes("..")) { throw new Error(`${label} must stay inside the Native change`); } } function parseSpecChange(value, index) { - const item = record7(value, `spec_changes[${index}]`); + const item = record3(value, `spec_changes[${index}]`); rejectUnknown3(item, SPEC_CHANGE_KEYS, `spec_changes[${index}]`); if (typeof item.capability !== "string") throw new Error("spec change capability is required"); assertCapabilityId(item.capability); @@ -14817,12 +14006,12 @@ function parseSpecChange(value, index) { throw new Error(`Create spec ${item.capability} requires null base_hash`); } else if (item.operation === "replace") { if (!source) throw new Error(`Replace spec ${item.capability} requires source`); - if (typeof baseHash !== "string" || !HASH_PATTERN7.test(baseHash)) { + if (typeof baseHash !== "string" || !HASH_PATTERN3.test(baseHash)) { throw new Error(`Replace spec ${item.capability} requires a SHA-256 base_hash`); } } else { if (source !== void 0) throw new Error(`Remove spec ${item.capability} forbids source`); - if (typeof baseHash !== "string" || !HASH_PATTERN7.test(baseHash)) { + if (typeof baseHash !== "string" || !HASH_PATTERN3.test(baseHash)) { throw new Error(`Remove spec ${item.capability} requires a SHA-256 base_hash`); } } @@ -14890,7 +14079,7 @@ function parseChangeFields(root, knownKeys) { }; } function parseLegacyNativeChangeValue(value) { - const root = record7(value, NATIVE_CHANGE_STATE_FILE); + const root = record3(value, NATIVE_CHANGE_STATE_FILE); if (root.schema !== NATIVE_LEGACY_CHANGE_SCHEMA) { throw new Error(`Expected ${NATIVE_LEGACY_CHANGE_SCHEMA}`); } @@ -14917,20 +14106,20 @@ function contentAddressedRef(value, label, kind) { } function approvedContractHash(value) { if (value === void 0 || value === null) return null; - if (typeof value !== "string" || !HASH_PATTERN7.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN3.test(value)) { throw new Error("Native approved_contract_hash must be null or a SHA-256 hash"); } return value; } function verificationProtocol(value) { if (value === void 0) return "legacy-v1"; - if (value !== "legacy-v1" && value !== "signed-v2") { - throw new Error("Native verification_protocol must be legacy-v1 or signed-v2"); + if (value !== "legacy-v1") { + throw new Error("Native verification_protocol must be legacy-v1"); } return value; } function parseV2NativeChangeValue(value) { - const root = record7(value, NATIVE_CHANGE_STATE_FILE); + const root = record3(value, NATIVE_CHANGE_STATE_FILE); if (root.schema !== NATIVE_V2_CHANGE_SCHEMA) { throw new Error(`Expected ${NATIVE_V2_CHANGE_SCHEMA}`); } @@ -14949,7 +14138,7 @@ function parseV2NativeChangeValue(value) { }; } function parseNativeChangeValue(value) { - const root = record7(value, NATIVE_CHANGE_STATE_FILE); + const root = record3(value, NATIVE_CHANGE_STATE_FILE); if (root.schema !== NATIVE_CHANGE_SCHEMA) { if (root.schema === NATIVE_LEGACY_CHANGE_SCHEMA || root.schema === NATIVE_V2_CHANGE_SCHEMA) { const previous = root.schema === NATIVE_LEGACY_CHANGE_SCHEMA ? parseLegacyNativeChangeValue(root) : parseV2NativeChangeValue(root); @@ -15003,7 +14192,7 @@ function parseNativeChangeValue(value) { }; } function inspectNativeChangeValue(value) { - const root = record7(value, NATIVE_CHANGE_STATE_FILE); + const root = record3(value, NATIVE_CHANGE_STATE_FILE); if (root.schema === NATIVE_LEGACY_CHANGE_SCHEMA) { const state2 = parseLegacyNativeChangeValue(root); return { @@ -15113,15 +14302,15 @@ function nativeV2ChangeDocument(state) { } function nativeChangeDir(paths, name) { assertNativeName(name); - const target = path21.join(paths.changesDir, name); + const target = path18.join(paths.changesDir, name); if (!isInsidePath(paths.changesDir, target)) throw new Error("Native change path escaped"); return target; } async function hasPendingNativeSchemaMigration(paths, name) { - const file = path21.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json"); + const file = path18.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json"); await resolveContainedNativePath(paths.nativeRoot, file); try { - await fs15.lstat(file); + await fs13.lstat(file); return true; } catch (error) { if (error.code === "ENOENT") return false; @@ -15129,10 +14318,10 @@ async function hasPendingNativeSchemaMigration(paths, name) { } } async function hasPendingNativeCheckpointRecovery(paths, name) { - const file = path21.join(nativeChangeDir(paths, name), "runtime", "checkpoint-journal.json"); + const file = path18.join(nativeChangeDir(paths, name), "runtime", "checkpoint-journal.json"); await resolveContainedNativePath(paths.nativeRoot, file); try { - await fs15.lstat(file); + await fs13.lstat(file); return true; } catch (error) { if (error.code === "ENOENT") return false; @@ -15148,42 +14337,19 @@ async function createNativeChange(options) { } async function createNativeChangeLocked(options) { assertNativeName(options.name); - const verificationProtocol2 = options.verificationProtocol ?? (options.creationAuthorization ? "signed-v2" : "legacy-v1"); - let signedCreation = null; - if (verificationProtocol2 === "signed-v2") { - const policy = await readNativeReviewTrustPolicy(options.paths).catch((error) => { - throw new Error( - "New signed-v2 Native changes require a review trust policy before creation", - { cause: error } - ); - }); - if (!options.creationAuthorization) { - throw new Error( - "New signed-v2 Native changes require a controller-signed creation authorization" - ); - } - signedCreation = { - policy, - authorization: await verifyNativeCreationAuthorization({ - paths: options.paths, - policyHash: policy.policyHash, - authorization: options.creationAuthorization, - change: options.name - }) - }; - } + const verificationProtocol2 = options.verificationProtocol ?? "legacy-v1"; const changeDir = nativeChangeDir(options.paths, options.name); await resolveContainedNativePath(options.paths.nativeRoot, changeDir); let createdChangeDir = false; try { try { - await fs15.mkdir(changeDir, { recursive: false }); + await fs13.mkdir(changeDir, { recursive: false }); createdChangeDir = true; } catch (error) { if (error.code === "ENOENT") { - await fs15.mkdir(options.paths.changesDir, { recursive: true }); + await fs13.mkdir(options.paths.changesDir, { recursive: true }); try { - await fs15.mkdir(changeDir, { recursive: false }); + await fs13.mkdir(changeDir, { recursive: false }); createdChangeDir = true; } catch (retryError) { if (retryError.code === "EEXIST") { @@ -15221,9 +14387,9 @@ async function createNativeChangeLocked(options) { run_id: null }; await Promise.all([ - fs15.mkdir(path21.join(changeDir, "specs"), { recursive: true }), - fs15.mkdir(path21.join(changeDir, "runtime", "checkpoints"), { recursive: true }), - atomicWriteText(path21.join(changeDir, "brief.md"), NATIVE_BRIEF_TEMPLATE) + fs13.mkdir(path18.join(changeDir, "specs"), { recursive: true }), + fs13.mkdir(path18.join(changeDir, "runtime", "checkpoints"), { recursive: true }), + atomicWriteText(path18.join(changeDir, "brief.md"), NATIVE_BRIEF_TEMPLATE) ]); const projectConfig = await readProjectConfig(options.paths.projectRoot); const snapshot2 = projectConfig?.native.snapshot ?? DEFAULT_NATIVE_SNAPSHOT_CONFIG; @@ -15257,27 +14423,7 @@ async function createNativeChangeLocked(options) { baseline.policy?.hash ?? null ); } - let creationBaseline = baseline; - if (signedCreation) { - const policyText = `${JSON.stringify(signedCreation.policy, null, 2)} -`; - const policySnapshotHash = createHash12("sha256").update(policyText).digest("hex"); - const policySnapshotRef = `runtime/trust/review-policy-${signedCreation.policy.policyHash}.json`; - await fs15.mkdir(path21.join(changeDir, "runtime", "trust"), { recursive: true }); - await atomicWriteText(path21.join(changeDir, ...policySnapshotRef.split("/")), policyText); - creationBaseline = { - ...baseline, - creation: { - schema: "comet.native.change-creation-binding.v1", - protocol: "signed-v2", - policyHash: signedCreation.policy.policyHash, - policySnapshotRef, - policySnapshotHash, - authorization: signedCreation.authorization - } - }; - } - await writeNativeBaselineManifest(options.paths, state.name, creationBaseline); + await writeNativeBaselineManifest(options.paths, state.name, baseline); await createNativeChangeFile(options.paths, state); await writeNativeWorkspaceIdentity({ paths: options.paths, @@ -15287,13 +14433,13 @@ async function createNativeChangeLocked(options) { }); return state; } catch (error) { - if (createdChangeDir) await fs15.rm(changeDir, { recursive: true, force: true }); + if (createdChangeDir) await fs13.rm(changeDir, { recursive: true, force: true }); throw error; } } var NATIVE_CHANGE_DOCUMENT_MAX_BYTES = 256 * 1024; -async function readChangeDocumentFile(file, root = path21.dirname(file)) { - const ref = path21.relative(root, file).split(path21.sep).join("/"); +async function readChangeDocumentFile(file, root = path18.dirname(file)) { + const ref = path18.relative(root, file).split(path18.sep).join("/"); const source = await readNativeBoundedTextFile({ root, ref, @@ -15306,7 +14452,7 @@ async function readChangeDocumentFile(file, root = path21.dirname(file)) { return document.toJS(); } async function inspectNativeChange(paths, name) { - const file = path21.join(nativeChangeDir(paths, name), NATIVE_CHANGE_STATE_FILE); + const file = path18.join(nativeChangeDir(paths, name), NATIVE_CHANGE_STATE_FILE); await resolveContainedNativePath(paths.nativeRoot, file); const inspection = inspectNativeChangeValue(await readChangeDocumentFile(file, paths.nativeRoot)); if (inspection.state && inspection.state.name !== name) { @@ -15327,67 +14473,10 @@ async function inspectNativeChange(paths, name) { return inspection; } async function assertNativeVerificationProtocolBinding(paths, state) { - const controllerTrust = await readNativeControllerTrustProject(paths.projectRoot); - const controllerRequiresSigned = controllerTrust !== null && !controllerTrust.legacyChanges.includes(state.name); - if (controllerRequiresSigned && state.verification_protocol !== "signed-v2") { - throw new Error( - `Native verification protocol does not match its creation baseline (controller-backed): expected signed-v2 for ${state.name}` - ); - } - if (controllerTrust !== null && controllerTrust.legacyChanges.includes(state.name) && state.verification_protocol !== "legacy-v1") { - throw new Error(`Native controller trust marks ${state.name} as legacy-v1`); - } const baseline = await readNativeBaselineManifest(paths, state.name); - if (baseline === null) { - if (state.verification_protocol === "signed-v2" || controllerRequiresSigned) { - throw new Error( - `Native signed-v2 verification protocol has no creation baseline: ${state.name}` - ); - } - return; - } - const expectedProtocol = baseline.creation ? "signed-v2" : "legacy-v1"; - if (state.verification_protocol !== expectedProtocol) { - throw new Error( - `Native verification protocol does not match its creation baseline: expected ${expectedProtocol}, got ${state.verification_protocol}` - ); - } - if (expectedProtocol === "signed-v2") { - if (!controllerTrust) { - throw new Error("Native signed-v2 creation has no controller-owned trust root"); - } - const creation = baseline.creation; - const changeDir = nativeChangeDir(paths, state.name); - const snapshot2 = await readNativeProtectedTextFile({ - root: nativeChangeDir(paths, state.name), - file: path21.join(changeDir, ...creation.policySnapshotRef.split("/")), - maxBytes: 64 * 1024, - label: "Native creation-time trust policy snapshot" - }); - if (createHash12("sha256").update(snapshot2.text).digest("hex") !== creation.policySnapshotHash) { - throw new Error("Native creation-time trust policy snapshot hash mismatch"); - } - let policy; - try { - policy = JSON.parse(snapshot2.text); - } catch (error) { - throw new Error("Native creation-time trust policy snapshot is invalid JSON", { - cause: error - }); - } - const verifiedPolicy = verifyNativeReviewTrustPolicy( - policy, - controllerTrust.controllerIdentity - ); - if (verifiedPolicy.policyHash !== creation.policyHash) { - throw new Error("Native creation-time trust policy snapshot does not match its binding"); - } - await verifyNativeCreationAuthorization({ - paths, - policyHash: creation.policyHash, - authorization: creation.authorization, - change: state.name - }); + if (baseline === null) return; + if (state.verification_protocol !== "legacy-v1") { + throw new Error(`Native verification protocol is unsupported: ${state.verification_protocol}`); } } async function readNativeChange(paths, name) { @@ -15401,10 +14490,10 @@ async function readNativeChange(paths, name) { return inspection.state; } async function createNativeChangeFile(paths, state) { - const file = path21.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE); + const file = path18.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE); await resolveContainedNativePath(paths.nativeRoot, file); try { - await fs15.access(file); + await fs13.access(file); throw new Error(`Native change state already exists: ${state.name}`); } catch (error) { if (error.code !== "ENOENT") throw error; @@ -15451,7 +14540,7 @@ async function compareAndSwapNativeChangeLocked(paths, state, expectedRevision, throw new Error("Native verification protocol changed outside a revisioned state transition"); } await assertNativeTrajectoryHealthy(paths, state.name); - const file = path21.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE); + const file = path18.join(nativeChangeDir(paths, state.name), NATIVE_CHANGE_STATE_FILE); await resolveContainedNativePath(paths.nativeRoot, file); return compareAndSwapNativeChangeFile(file, state, expectedRevision); } @@ -15503,15 +14592,15 @@ function markdownSections(source) { return sections; } async function readContainedFile(root, relativeRef) { - const target = path22.resolve(root, ...relativeRef.split(/[\\/]/u)); + const target = path19.resolve(root, ...relativeRef.split(/[\\/]/u)); if (!isInsidePath(root, target)) throw new Error(`Artifact escapes Native change: ${relativeRef}`); - const realRoot = await fs16.realpath(root); - const realTarget = await fs16.realpath(target); + const realRoot = await fs14.realpath(root); + const realTarget = await fs14.realpath(target); if (!isInsidePath(realRoot, realTarget)) { throw new Error(`Artifact symlink escapes Native change: ${relativeRef}`); } - if (!(await fs16.stat(realTarget)).isFile()) + if (!(await fs14.stat(realTarget)).isFile()) throw new Error(`Artifact is not a file: ${relativeRef}`); return realTarget; } @@ -15592,7 +14681,7 @@ async function validateNativeVerification(changeDir, reportRef) { return result(findings); } function canonicalSpecPath(paths, capability) { - return path22.join(paths.specsDir, capability, "spec.md"); + return path19.join(paths.specsDir, capability, "spec.md"); } async function validateNativeSpecChanges(paths, state) { const findings = []; @@ -15652,16 +14741,101 @@ async function resolveNativeArtifactFile(changeDir, relativeRef) { return readContainedFile(changeDir, relativeRef); } -// domains/comet-native/native-archive-inspection.ts -import { promises as fs22 } from "node:fs"; -import path38 from "node:path"; - +// domains/comet-native/native-archive-inspection.ts +import { promises as fs22 } from "node:fs"; +import path38 from "node:path"; + +// domains/comet-native/native-archive-preflight.ts +import path20 from "node:path"; + +// domains/comet-native/native-canonical-hash.ts +import { createHash as createHash10 } from "crypto"; +function invalidCanonicalJson(detail) { + throw new TypeError(`Value is not valid canonical JSON: ${detail}`); +} +function canonicalArray(value, ancestors) { + if (ancestors.has(value)) invalidCanonicalJson("cyclic structures are not supported"); + ancestors.add(value); + try { + const enumerableKeys = Object.keys(value); + for (let index = 0; index < value.length; index += 1) { + if (!Object.prototype.hasOwnProperty.call(value, index)) { + invalidCanonicalJson("sparse arrays are not supported"); + } + } + if (enumerableKeys.length !== value.length || enumerableKeys.some((key, index) => key !== String(index))) { + invalidCanonicalJson("arrays must not have named enumerable properties"); + } + if (Object.getOwnPropertySymbols(value).length > 0) { + invalidCanonicalJson("symbol properties are not supported"); + } + return `[${value.map((entry2) => canonicalValue(entry2, ancestors)).join(",")}]`; + } finally { + ancestors.delete(value); + } +} +function canonicalObject(value, ancestors) { + const prototype = Object.getPrototypeOf(value); + if (prototype !== Object.prototype && prototype !== null) { + invalidCanonicalJson("only plain objects are supported"); + } + if (ancestors.has(value)) invalidCanonicalJson("cyclic structures are not supported"); + if (Object.getOwnPropertySymbols(value).length > 0) { + invalidCanonicalJson("symbol properties are not supported"); + } + ancestors.add(value); + try { + const descriptors = Object.getOwnPropertyDescriptors(value); + const keys = Object.keys(value).sort(); + const fields = keys.map((key) => { + const descriptor = descriptors[key]; + if (!descriptor || !("value" in descriptor)) { + invalidCanonicalJson("accessor properties are not supported"); + } + return `${JSON.stringify(key)}:${canonicalValue(descriptor.value, ancestors)}`; + }); + return `{${fields.join(",")}}`; + } finally { + ancestors.delete(value); + } +} +function canonicalValue(value, ancestors) { + if (value === null) return "null"; + switch (typeof value) { + case "boolean": + return value ? "true" : "false"; + case "string": + return JSON.stringify(value); + case "number": + if (!Number.isFinite(value)) invalidCanonicalJson("numbers must be finite"); + return JSON.stringify(Object.is(value, -0) ? 0 : value); + case "object": + return Array.isArray(value) ? canonicalArray(value, ancestors) : canonicalObject(value, ancestors); + case "bigint": + case "function": + case "symbol": + case "undefined": + return invalidCanonicalJson(`${typeof value} values are not supported`); + } + return invalidCanonicalJson("unsupported value type"); +} +function canonicalJson(value) { + return canonicalValue(value, /* @__PURE__ */ new Set()); +} +function canonicalHash(tag, value) { + if (tag.length === 0) throw new TypeError("Canonical hash tag must be non-empty"); + if (/[\r\n]/u.test(tag)) { + throw new TypeError("Canonical hash tag must not contain a line break"); + } + return createHash10("sha256").update(`${tag} +${canonicalJson(value)}`).digest("hex"); +} + // domains/comet-native/native-archive-preflight.ts -import path23 from "node:path"; var NATIVE_ARCHIVE_PREFLIGHT_SCHEMA = "comet.native.archive-preflight.v1"; var PREFLIGHT_HASH_TAG = "comet.native.archive-preflight.v1"; var OPERATION_HASH_TAG = "comet.native.archive-operation.v1"; -var HASH_PATTERN8 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN4 = /^[a-f0-9]{64}$/u; var NAME_PATTERN2 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var FINDING_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; function compareText(left, right) { @@ -15670,7 +14844,7 @@ function compareText(left, right) { return 0; } function hash(value, label) { - if (typeof value !== "string" || !HASH_PATTERN8.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN4.test(value)) { throw new Error(`${label} must be a SHA-256 hash`); } return value; @@ -15679,8 +14853,8 @@ function optionalHash(value, label) { return value === null ? null : hash(value, label); } function normalizedRef(value, label) { - const normalized = path23.posix.normalize(value); - if (typeof value !== "string" || value.length === 0 || value !== value.trim() || value.includes("\\") || path23.posix.isAbsolute(normalized) || /^(?:[A-Za-z]:|~)/u.test(value) || value.split("/").includes("..") || normalized !== value || normalized === "." || value.endsWith("/")) { + const normalized = path20.posix.normalize(value); + if (typeof value !== "string" || value.length === 0 || value !== value.trim() || value.includes("\\") || path20.posix.isAbsolute(normalized) || /^(?:[A-Za-z]:|~)/u.test(value) || value.split("/").includes("..") || normalized !== value || normalized === "." || value.endsWith("/")) { throw new Error(`${label} must be a normalized Native-relative ref`); } return value; @@ -15867,8 +15041,8 @@ function buildNativeArchivePreflight(input) { } // domains/comet-native/native-conflict-radar.ts -import { createHash as createHash13 } from "node:crypto"; -import path24 from "node:path"; +import { createHash as createHash11 } from "node:crypto"; +import path21 from "node:path"; var NATIVE_CONFLICT_RADAR_SCHEMA = "comet.native.conflict-radar.v1"; var NATIVE_CONFLICT_RADAR_LIMITS = Object.freeze({ maxChanges: 32, @@ -15885,7 +15059,7 @@ var NATIVE_CONFLICT_RADAR_LIMITS = Object.freeze({ }); var RADAR_HASH_TAG = "comet.native.conflict-radar.v1"; var SIGNAL_SET_HASH_TAG = "comet.native.conflict-radar-signals.v1"; -var HASH_PATTERN9 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN5 = /^[a-f0-9]{64}$/u; var NAME_PATTERN3 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var CHANGE_KEYS2 = /* @__PURE__ */ new Set([ "name", @@ -15908,10 +15082,10 @@ function strictRecord(value, keys, label) { if (value === null || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } - const record13 = value; - const unknown = Object.keys(record13).filter((key) => !keys.has(key)); + const record12 = value; + const unknown = Object.keys(record12).filter((key) => !keys.has(key)); if (unknown.length > 0) throw new Error(`${label} has unknown field(s): ${unknown.join(", ")}`); - return record13; + return record12; } function boundedText(value, label, maxBytes) { if (typeof value !== "string" || value.length === 0 || value !== value.trim()) { @@ -15922,78 +15096,78 @@ function boundedText(value, label, maxBytes) { } function normalizedProjectPath(value, label) { const candidate = boundedText(value, label, NATIVE_CONFLICT_RADAR_LIMITS.maxArtifactPathBytes); - const normalized = path24.posix.normalize(candidate); + const normalized = path21.posix.normalize(candidate); if (candidate.includes("\\") || Array.from(candidate).some((character) => { const code = character.codePointAt(0) ?? 0; return code <= 31 || code === 127; - }) || /^(?:[A-Za-z]:|~)/u.test(candidate) || path24.posix.isAbsolute(normalized) || candidate.split("/").includes("..") || normalized !== candidate || normalized === "." || candidate.endsWith("/")) { + }) || /^(?:[A-Za-z]:|~)/u.test(candidate) || path21.posix.isAbsolute(normalized) || candidate.split("/").includes("..") || normalized !== candidate || normalized === "." || candidate.endsWith("/")) { throw new Error(`${label} must be a normalized project-relative path`); } return candidate; } function normalizedHash(value, label) { - if (typeof value !== "string" || !HASH_PATTERN9.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN5.test(value)) { throw new Error(`${label} must be a SHA-256 hash`); } return value; } function normalizeSpec2(value, label) { - const record13 = strictRecord(value, SPEC_KEYS, label); + const record12 = strictRecord(value, SPEC_KEYS, label); const capability = boundedText( - record13.capability, + record12.capability, `${label} capability`, NATIVE_CONFLICT_RADAR_LIMITS.maxCapabilityBytes ); if (!NAME_PATTERN3.test(capability)) throw new Error(`${label} capability is invalid`); - if (record13.operation !== "create" && record13.operation !== "replace" && record13.operation !== "remove") { + if (record12.operation !== "create" && record12.operation !== "replace" && record12.operation !== "remove") { throw new Error(`${label} operation is invalid`); } - const baseHash = record13.baseHash === null ? null : normalizedHash(record13.baseHash, `${label} canonical base hash`); - if (record13.operation === "create" && baseHash !== null) { + const baseHash = record12.baseHash === null ? null : normalizedHash(record12.baseHash, `${label} canonical base hash`); + if (record12.operation === "create" && baseHash !== null) { throw new Error(`${label} create operation requires a null canonical base hash`); } - if (record13.operation !== "create" && baseHash === null) { - throw new Error(`${label} ${record13.operation} operation requires a canonical base hash`); + if (record12.operation !== "create" && baseHash === null) { + throw new Error(`${label} ${record12.operation} operation requires a canonical base hash`); } - return { capability, operation: record13.operation, baseHash }; + return { capability, operation: record12.operation, baseHash }; } function normalizeArtifact(value, label) { - const record13 = strictRecord(value, ARTIFACT_KEYS, label); - if (record13.kind !== "file" && record13.kind !== "directory") { + const record12 = strictRecord(value, ARTIFACT_KEYS, label); + if (record12.kind !== "file" && record12.kind !== "directory") { throw new Error(`${label} kind is invalid`); } - return { path: normalizedProjectPath(record13.path, `${label} path`), kind: record13.kind }; + return { path: normalizedProjectPath(record12.path, `${label} path`), kind: record12.kind }; } function normalizeChange(value, index) { - const record13 = strictRecord(value, CHANGE_KEYS2, `Conflict radar change ${index}`); + const record12 = strictRecord(value, CHANGE_KEYS2, `Conflict radar change ${index}`); const name = boundedText( - record13.name, + record12.name, `Conflict radar change ${index} name`, NATIVE_CONFLICT_RADAR_LIMITS.maxNameBytes ); if (!NAME_PATTERN3.test(name)) throw new Error(`Conflict radar change ${index} name is invalid`); - if (!Number.isSafeInteger(record13.revision) || record13.revision < 1) { + if (!Number.isSafeInteger(record12.revision) || record12.revision < 1) { throw new Error(`Conflict radar change ${name} revision must be a positive safe integer`); } - if (!Array.isArray(record13.specs)) { + if (!Array.isArray(record12.specs)) { throw new Error(`Conflict radar change ${name} specs must be an array`); } - if (record13.specs.length > NATIVE_CONFLICT_RADAR_LIMITS.maxSpecsPerChange) { + if (record12.specs.length > NATIVE_CONFLICT_RADAR_LIMITS.maxSpecsPerChange) { throw new Error(`Conflict radar change ${name} exceeds the spec budget`); } - if (!Array.isArray(record13.declaredArtifacts)) { + if (!Array.isArray(record12.declaredArtifacts)) { throw new Error(`Conflict radar change ${name} declared artifacts must be an array`); } - if (record13.declaredArtifacts.length > NATIVE_CONFLICT_RADAR_LIMITS.maxArtifactsPerChange) { + if (record12.declaredArtifacts.length > NATIVE_CONFLICT_RADAR_LIMITS.maxArtifactsPerChange) { throw new Error(`Conflict radar change ${name} exceeds the declared artifact budget`); } - const specs = record13.specs.map( + const specs = record12.specs.map( (spec, specIndex) => normalizeSpec2(spec, `Conflict radar change ${name} spec ${specIndex}`) ).sort((left, right) => compareText2(left.capability, right.capability)); if (new Set(specs.map((spec) => spec.capability)).size !== specs.length) { throw new Error(`Conflict radar change ${name} has duplicate capabilities`); } - const declaredArtifacts = record13.declaredArtifacts.map( + const declaredArtifacts = record12.declaredArtifacts.map( (artifact, artifactIndex) => normalizeArtifact(artifact, `Conflict radar change ${name} artifact ${artifactIndex}`) ).sort( (left, right) => compareText2(left.path, right.path) || compareText2(left.kind, right.kind) @@ -16002,13 +15176,13 @@ function normalizeChange(value, index) { if (new Set(artifactPaths).size !== artifactPaths.length) { throw new Error(`Conflict radar change ${name} has duplicate or conflicting artifact paths`); } - const workspaceIdentityHash = record13.workspaceIdentityHash === void 0 || record13.workspaceIdentityHash === null ? null : normalizedHash( - record13.workspaceIdentityHash, + const workspaceIdentityHash = record12.workspaceIdentityHash === void 0 || record12.workspaceIdentityHash === null ? null : normalizedHash( + record12.workspaceIdentityHash, `Conflict radar change ${name} workspace identity hash` ); return { name, - revision: record13.revision, + revision: record12.revision, specs, declaredArtifacts, workspaceIdentityHash @@ -16067,7 +15241,7 @@ function workspaceRelationship(left, right) { } function relationship(left, right) { const visibleSignals = []; - const signalHasher = createHash13("sha256").update(`${SIGNAL_SET_HASH_TAG} + const signalHasher = createHash11("sha256").update(`${SIGNAL_SET_HASH_TAG} [`); let signalCount = 0; let hasDefiniteConflict = false; @@ -16165,13 +15339,13 @@ function buildNativeConflictRadar(input) { } // domains/comet-native/native-evidence-storage.ts -import { createHash as createHash15 } from "node:crypto"; -import { promises as fs17 } from "node:fs"; -import path28 from "node:path"; +import { createHash as createHash14 } from "node:crypto"; +import { promises as fs15 } from "node:fs"; +import path25 from "node:path"; // domains/comet-native/native-verification-scope.ts -import { createHash as createHash14 } from "node:crypto"; -import path25 from "path"; +import { createHash as createHash12 } from "node:crypto"; +import path22 from "path"; var NATIVE_IMPLEMENTATION_SCOPE_SCHEMA = "comet.native.implementation-scope.v2"; var NATIVE_SNAPSHOT_PROJECTION_SCHEMA = "comet.native.content-snapshot-projection.v1"; var SNAPSHOT_PROJECTION_HASH_TAG = "comet.native.content-snapshot-projection.v1"; @@ -16204,8 +15378,8 @@ function projectRelativePath(value, label) { if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0") || value.endsWith("/") || /^[a-zA-Z]:/u.test(value)) { throw new Error(`${label} must be a normalized project-relative path`); } - const normalized = path25.posix.normalize(value); - if (normalized !== value || normalized === "." || normalized === ".." || normalized.startsWith("../") || path25.posix.isAbsolute(normalized)) { + const normalized = path22.posix.normalize(value); + if (normalized !== value || normalized === "." || normalized === ".." || normalized.startsWith("../") || path22.posix.isAbsolute(normalized)) { throw new Error(`${label} must stay inside the project root`); } return value; @@ -16262,7 +15436,7 @@ function fitSnapshotProjectionBudget(value) { const omitted = [...value.omitted]; let omittedCount = value.omittedCount; let overflowCount = value.omissionOverflow?.count ?? 0; - const overflowHash = createHash14("sha256"); + const overflowHash = createHash12("sha256"); overflowHash.update(`${SNAPSHOT_PROJECTION_OVERFLOW_HASH_TAG} `); if (value.omissionOverflow) { @@ -16571,7 +15745,7 @@ function parseScopeDetailOverflow(scope) { return { ...counts, hash: hash8 }; } function hashScopeDetailOverflow(options) { - const hash8 = createHash14("sha256"); + const hash8 = createHash12("sha256"); hash8.update(`${SCOPE_DETAIL_OVERFLOW_HASH_TAG} `); let changeIndex = 0; @@ -17210,7 +16384,7 @@ function rebuildNativeImplementationScopeBundle(input) { } // domains/comet-native/native-verification-evidence.ts -import path26 from "node:path"; +import path23 from "node:path"; // domains/comet-native/native-redaction.ts var AUTHORIZATION_PATTERN = /\b(Bearer|Basic)\s+[^\s"']+/giu; @@ -17226,7 +16400,7 @@ function redactNativeCredentialText(value) { // domains/comet-native/native-independent-review.ts var HASH_TAG = "comet.native.independent-review.v1"; -var HASH_PATTERN10 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN6 = /^[a-f0-9]{64}$/u; var MAX_TEXT = 2e3; function object(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { @@ -17234,7 +16408,7 @@ function object(value, label) { } return value; } -function exactKeys5(value, keys, label) { +function exactKeys(value, keys, label) { const actual = Object.keys(value).sort(); const expected = [...keys].sort(); if (JSON.stringify(actual) !== JSON.stringify(expected)) @@ -17248,7 +16422,7 @@ function text(value, label) { } function reviewContent(value, expectedAcceptanceIds) { const root = object(value, "Native independent review"); - exactKeys5( + exactKeys( root, ["implementation_author", "reviewer", "acceptance_ids", "checked", "findings"], "Native independent review" @@ -17267,7 +16441,7 @@ function reviewContent(value, expectedAcceptanceIds) { if (new Set(acceptanceIds).size !== acceptanceIds.length || JSON.stringify([...acceptanceIds].sort()) !== JSON.stringify(expected)) throw new Error("Native independent review does not cover every acceptance ID"); const checked = object(root.checked, "Native independent review checked"); - exactKeys5( + exactKeys( checked, ["unified_io", "adversarial_paths", "generated_assets", "lifecycle_eval"], "Native independent review checked" @@ -17278,7 +16452,7 @@ function reviewContent(value, expectedAcceptanceIds) { throw new Error("Native independent review findings are invalid"); const findings = root.findings.map((entry2, index) => { const finding = object(entry2, `Native independent review finding ${index}`); - exactKeys5( + exactKeys( finding, ["severity", "status", "summary"], `Native independent review finding ${index}` @@ -17309,7 +16483,7 @@ function reviewContent(value, expectedAcceptanceIds) { } function parseNativeIndependentReview(value, expectedAcceptanceIds) { const root = object(value, "Native independent review"); - exactKeys5( + exactKeys( root, [ "schema", @@ -17328,7 +16502,7 @@ function parseNativeIndependentReview(value, expectedAcceptanceIds) { void _hash; void _schema; const content = reviewContent(input, expectedAcceptanceIds); - if (typeof root.review_hash !== "string" || !HASH_PATTERN10.test(root.review_hash) || canonicalHash(HASH_TAG, content) !== root.review_hash) { + if (typeof root.review_hash !== "string" || !HASH_PATTERN6.test(root.review_hash) || canonicalHash(HASH_TAG, content) !== root.review_hash) { throw new Error("Native independent review hash is invalid"); } return { ...content, reviewHash: root.review_hash }; @@ -17349,7 +16523,7 @@ function isNativeHighRiskScope(changes) { } // domains/comet-native/native-verification-evidence.ts -var HASH_PATTERN11 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN7 = /^[a-f0-9]{64}$/u; var MISSING_ACCEPTANCE_DETAIL_LIMIT = 8; var ACCEPTANCE_TRACE_HASH_TAG = "comet.native.acceptance-trace.v2"; var LEGACY_ACCEPTANCE_TRACE_HASH_TAG = "comet.native.acceptance-trace.v1"; @@ -17357,7 +16531,7 @@ var PARTIAL_ALLOWANCE_HASH_TAG = "comet.native.partial-allowance.v1"; var VERIFICATION_ENVELOPE_HASH_TAG = "comet.native.verification-evidence.v2"; var LEGACY_VERIFICATION_ENVELOPE_HASH_TAG = "comet.native.verification-evidence.v1"; function hash2(value, label) { - if (!HASH_PATTERN11.test(value)) throw new Error(`${label} must be a SHA-256 hash`); + if (!HASH_PATTERN7.test(value)) throw new Error(`${label} must be a SHA-256 hash`); return value; } function positiveRevision2(value) { @@ -17380,11 +16554,11 @@ function requiredText(value, label, max = 2e3) { return normalized; } function portableRef(value, label) { - const normalized = path26.posix.normalize(value); + const normalized = path23.posix.normalize(value); if (value.length === 0 || value !== value.trim() || value.includes("\\") || Array.from(value).some((character) => { const code = character.codePointAt(0) ?? 0; return code <= 31 || code === 127; - }) || path26.posix.isAbsolute(normalized) || /^(?:[A-Za-z]:|~)/u.test(value) || value.split("/").includes("..") || normalized !== value || normalized === "." || value.endsWith("/")) { + }) || path23.posix.isAbsolute(normalized) || /^(?:[A-Za-z]:|~)/u.test(value) || value.split("/").includes("..") || normalized !== value || normalized === "." || value.endsWith("/")) { throw new Error(`${label} must be a normalized relative ref`); } return value; @@ -17674,7 +16848,7 @@ function parseNativeAcceptanceEvidenceTrace(value) { ], "Native acceptance trace" ); - if (root.schema !== "comet.native.acceptance-trace.v2" || typeof root.nativeRootRef !== "string" || typeof root.criteriaHash !== "string" || !HASH_PATTERN11.test(root.criteriaHash) || !Number.isSafeInteger(root.total) || !Number.isSafeInteger(root.evidenced) || !Number.isSafeInteger(root.skipped) || !Array.isArray(root.entries)) { + if (root.schema !== "comet.native.acceptance-trace.v2" || typeof root.nativeRootRef !== "string" || typeof root.criteriaHash !== "string" || !HASH_PATTERN7.test(root.criteriaHash) || !Number.isSafeInteger(root.total) || !Number.isSafeInteger(root.evidenced) || !Number.isSafeInteger(root.skipped) || !Array.isArray(root.entries)) { throw new Error("Native acceptance trace is invalid"); } const nativeRootRef3 = portableRef(root.nativeRootRef, "Native root ref"); @@ -17748,7 +16922,7 @@ function parseNativeLegacyAcceptanceEvidenceTrace(value) { ], "Legacy Native acceptance trace" ); - if (root.schema !== "comet.native.acceptance-trace.v1" || typeof root.nativeRootRef !== "string" || typeof root.criteriaHash !== "string" || !HASH_PATTERN11.test(root.criteriaHash) || !Number.isSafeInteger(root.total) || !Number.isSafeInteger(root.evidenced) || !Number.isSafeInteger(root.skipped) || !Array.isArray(root.entries)) { + if (root.schema !== "comet.native.acceptance-trace.v1" || typeof root.nativeRootRef !== "string" || typeof root.criteriaHash !== "string" || !HASH_PATTERN7.test(root.criteriaHash) || !Number.isSafeInteger(root.total) || !Number.isSafeInteger(root.evidenced) || !Number.isSafeInteger(root.skipped) || !Array.isArray(root.entries)) { throw new Error("Legacy Native acceptance trace is invalid"); } const nativeRootRef3 = portableRef(root.nativeRootRef, "Legacy Native root ref"); @@ -18090,7 +17264,7 @@ function parseNativeLegacyVerificationEvidenceEnvelope(root) { } // domains/comet-native/native-check-receipt-model.ts -import path27 from "node:path"; +import path24 from "node:path"; var NATIVE_CHECK_RECEIPT_SCHEMA = "comet.native.check-receipt.v1"; var NATIVE_CHECK_RECEIPT_HASH_TAG = "comet.native.check-receipt.v1"; var NATIVE_CHECK_POLICY = "scoped-text-safety"; @@ -18104,8 +17278,8 @@ var NATIVE_CHECK_LIMITS = Object.freeze({ maxTotalBytes: 8 * 1024 * 1024, maxIssues: 128 }); -var HASH_PATTERN12 = /^[a-f0-9]{64}$/u; -var CHANGE_NAME_PATTERN5 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; +var HASH_PATTERN8 = /^[a-f0-9]{64}$/u; +var CHANGE_NAME_PATTERN3 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var CHECKER_HASH_TAG = "comet.native.checker-policy.v1"; var CHECK_INPUT_HASH_TAG = "comet.native.check-input.v1"; var MAX_ISSUE_PATH_BYTES = 2048; @@ -18143,13 +17317,13 @@ var STALE_REASON_ORDER = [ "implementation-after-does-not-match-scope" ]; var STALE_REASONS = new Set(STALE_REASON_ORDER); -function record8(value, label) { +function record4(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } return value; } -function exactKeys6(value, expected, label) { +function exactKeys2(value, expected, label) { const keys = new Set(expected); const unknown = Object.keys(value).filter((key) => !keys.has(key)); const missing = expected.filter((key) => !(key in value)); @@ -18157,7 +17331,7 @@ function exactKeys6(value, expected, label) { if (missing.length > 0) throw new Error(`${label} is missing field(s): ${missing.join(", ")}`); } function hash3(value, label) { - if (typeof value !== "string" || !HASH_PATTERN12.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN8.test(value)) { throw new Error(`${label} must be a SHA-256 hash`); } return value; @@ -18185,17 +17359,17 @@ function projectRelativePath2(value, label) { if (typeof value !== "string" || value.length === 0 || Buffer.byteLength(value, "utf8") > MAX_ISSUE_PATH_BYTES || value.includes("\\") || value.includes("\0") || value.endsWith("/") || /^[a-zA-Z]:/u.test(value)) { throw new Error(`${label} must be a bounded normalized project-relative path`); } - const normalized = path27.posix.normalize(value); - if (normalized !== value || normalized === "." || normalized === ".." || normalized.startsWith("../") || path27.posix.isAbsolute(normalized)) { + const normalized = path24.posix.normalize(value); + if (normalized !== value || normalized === "." || normalized === ".." || normalized.startsWith("../") || path24.posix.isAbsolute(normalized)) { throw new Error(`${label} must stay inside the project root`); } return value; } function parseChecker(value) { - const checker = record8(value, "Native check receipt checker"); - exactKeys6(checker, ["policy", "version", "hash", "limits"], "Native check receipt checker"); - const limits = record8(checker.limits, "Native check receipt limits"); - exactKeys6( + const checker = record4(value, "Native check receipt checker"); + exactKeys2(checker, ["policy", "version", "hash", "limits"], "Native check receipt checker"); + const limits = record4(checker.limits, "Native check receipt limits"); + exactKeys2( limits, ["maxFiles", "maxFileBytes", "maxTotalBytes", "maxIssues"], "Native check receipt limits" @@ -18211,8 +17385,8 @@ function parseChecker(value) { }; } function parseContract(value) { - const contract = record8(value, "Native check receipt contract"); - exactKeys6(contract, ["expectedHash", "beforeHash", "afterHash"], "Native check receipt contract"); + const contract = record4(value, "Native check receipt contract"); + exactKeys2(contract, ["expectedHash", "beforeHash", "afterHash"], "Native check receipt contract"); return { expectedHash: hash3(contract.expectedHash, "Native check expected contract hash"), beforeHash: hash3(contract.beforeHash, "Native check before contract hash"), @@ -18220,8 +17394,8 @@ function parseContract(value) { }; } function parseImplementation(value) { - const implementation = record8(value, "Native check receipt implementation"); - exactKeys6( + const implementation = record4(value, "Native check receipt implementation"); + exactKeys2( implementation, ["scopeHash", "expectedSnapshotHash", "beforeSnapshotHash", "afterSnapshotHash"], "Native check receipt implementation" @@ -18240,8 +17414,8 @@ function parseImplementation(value) { }; } function parseCounts(value) { - const counts = record8(value, "Native check receipt counts"); - exactKeys6( + const counts = record4(value, "Native check receipt counts"); + exactKeys2( counts, [ "filesSelected", @@ -18280,8 +17454,8 @@ function parseIssues(value) { throw new Error("Native check receipt issues must be a bounded array"); } const issues = value.map((entry2, index) => { - const issue = record8(entry2, `Native check issue ${index}`); - exactKeys6(issue, ["path", "line", "kind"], `Native check issue ${index}`); + const issue = record4(entry2, `Native check issue ${index}`); + exactKeys2(issue, ["path", "line", "kind"], `Native check issue ${index}`); if (typeof issue.kind !== "string" || !ISSUE_KINDS.has(issue.kind)) { throw new Error(`Native check issue ${index} kind is invalid`); } @@ -18314,8 +17488,8 @@ function nativeCheckInputHash(value) { return canonicalHash(CHECK_INPUT_HASH_TAG, value); } function parseNativeCheckReceipt(value) { - const receipt = record8(value, "Native check receipt"); - exactKeys6( + const receipt = record4(value, "Native check receipt"); + exactKeys2( receipt, [ "schema", @@ -18340,7 +17514,7 @@ function parseNativeCheckReceipt(value) { if (receipt.schema !== NATIVE_CHECK_RECEIPT_SCHEMA) { throw new Error("Native check receipt schema is invalid"); } - if (typeof receipt.change !== "string" || Buffer.byteLength(receipt.change, "utf8") > 128 || !CHANGE_NAME_PATTERN5.test(receipt.change)) { + if (typeof receipt.change !== "string" || Buffer.byteLength(receipt.change, "utf8") > 128 || !CHANGE_NAME_PATTERN3.test(receipt.change)) { throw new Error("Native check receipt change name is invalid"); } const sourceRevision = positiveInteger3(receipt.sourceRevision, "Native check source revision"); @@ -18435,6 +17609,210 @@ function buildNativeCheckReceipt(input) { }); } +// domains/comet-native/native-review-identity.ts +import { + createHash as createHash13, + createPrivateKey, + createPublicKey, + generateKeyPairSync, + sign as cryptoSign, + verify as cryptoVerify +} from "node:crypto"; +var NATIVE_REVIEW_IDENTITY_SCHEMA = "comet.native.review-identity.v1"; +var NATIVE_REVIEW_KEYPAIR_SCHEMA = "comet.native.review-keypair.v1"; +var NATIVE_REVIEW_SIGNATURE_SCHEMA = "comet.native.review-signature.v1"; +var ALGORITHM = "ed25519"; +var HASH_PATTERN9 = /^[a-f0-9]{64}$/u; +var MAX_PUBLIC_KEY_TEXT = 512; +var MAX_PRIVATE_KEY_TEXT = 16384; +var MAX_SIGNATURE_TEXT = 256; +var SIGNATURE_CONTEXT = Buffer.from("comet.native.review-payload.v1\0", "utf8"); +function nativeReviewPrivateKeyFilePersistenceSupported(platform = process.platform) { + return platform !== "win32"; +} +function record5(value, label) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`${label} must be an object`); + } + return value; +} +function exactKeys3(value, expected, label) { + const actual = Object.keys(value).sort(); + const canonical = [...expected].sort(); + if (JSON.stringify(actual) !== JSON.stringify(canonical)) { + throw new Error(`${label} fields are invalid`); + } +} +function sha256(value) { + return createHash13("sha256").update(value).digest("hex"); +} +function payloadHash(value, label = "Native review payloadHash") { + if (typeof value !== "string" || !HASH_PATTERN9.test(value)) { + throw new Error(`${label} must be a lowercase SHA-256 hash`); + } + return value; +} +function canonicalBase64(value, label, maxCharacters, expectedBytes) { + if (typeof value !== "string" || value.length === 0 || value.length > maxCharacters || value.length % 4 !== 0) { + throw new Error(`${label} is invalid`); + } + const bytes = Buffer.from(value, "base64"); + if (bytes.length === 0 || bytes.toString("base64") !== value || expectedBytes !== void 0 && bytes.length !== expectedBytes) { + throw new Error(`${label} must use canonical base64`); + } + return bytes; +} +function publicKeyMaterial(value) { + const supplied = canonicalBase64(value, "Native review public key", MAX_PUBLIC_KEY_TEXT); + let key; + try { + key = createPublicKey({ key: supplied, format: "der", type: "spki" }); + } catch (error) { + throw new Error("Native review public key is invalid", { cause: error }); + } + if (key.type !== "public" || key.asymmetricKeyType !== "ed25519") { + throw new Error("Native review public key must be Ed25519"); + } + const der = key.export({ format: "der", type: "spki" }); + if (!Buffer.isBuffer(der) || !supplied.equals(der)) { + throw new Error("Native review public key must use canonical SPKI DER"); + } + return { key, der, text: der.toString("base64") }; +} +function privateKeyMaterial(value) { + const supplied = canonicalBase64(value, "Native review private key", MAX_PRIVATE_KEY_TEXT); + let key; + try { + key = createPrivateKey({ key: supplied, format: "der", type: "pkcs8" }); + } catch (error) { + throw new Error("Native review private key is invalid", { cause: error }); + } + if (key.type !== "private" || key.asymmetricKeyType !== "ed25519") { + throw new Error("Native review private key must be Ed25519"); + } + const der = key.export({ format: "der", type: "pkcs8" }); + if (!Buffer.isBuffer(der) || !supplied.equals(der)) { + throw new Error("Native review private key must use canonical PKCS8 DER"); + } + return { key, der }; +} +function signaturePayload(hash8) { + return Buffer.concat([SIGNATURE_CONTEXT, Buffer.from(hash8, "hex")]); +} +function parseNativeReviewSignature(value) { + const root = record5(value, "Native review signature"); + exactKeys3( + root, + ["schema", "algorithm", "keyId", "payloadHash", "signature"], + "Native review signature" + ); + if (root.schema !== NATIVE_REVIEW_SIGNATURE_SCHEMA || root.algorithm !== ALGORITHM || typeof root.keyId !== "string" || !HASH_PATTERN9.test(root.keyId)) { + throw new Error("Native review signature identity is invalid"); + } + const hash8 = payloadHash(root.payloadHash); + const signature = canonicalBase64( + root.signature, + "Native review signature", + MAX_SIGNATURE_TEXT, + 64 + ).toString("base64"); + return { + schema: NATIVE_REVIEW_SIGNATURE_SCHEMA, + algorithm: ALGORITHM, + keyId: root.keyId, + payloadHash: hash8, + signature + }; +} +function parseNativeReviewIdentity(value) { + const root = record5(value, "Native review identity"); + exactKeys3(root, ["schema", "algorithm", "keyId", "publicKey"], "Native review identity"); + if (root.schema !== NATIVE_REVIEW_IDENTITY_SCHEMA || root.algorithm !== ALGORITHM || typeof root.keyId !== "string" || !HASH_PATTERN9.test(root.keyId)) { + throw new Error("Native review identity keyId is invalid"); + } + const publicKey = publicKeyMaterial(root.publicKey); + const keyId = sha256(publicKey.der); + if (root.keyId !== keyId) { + throw new Error("Native review identity keyId does not match its public key"); + } + return { + schema: NATIVE_REVIEW_IDENTITY_SCHEMA, + algorithm: ALGORITHM, + keyId, + publicKey: publicKey.text + }; +} +function generateNativeReviewKeyPair() { + const generated = generateKeyPairSync("ed25519"); + const publicKey = generated.publicKey.export({ format: "der", type: "spki" }); + const privateKey = generated.privateKey.export({ format: "der", type: "pkcs8" }); + if (!Buffer.isBuffer(publicKey) || !Buffer.isBuffer(privateKey)) { + throw new Error("Native review key generation returned unsupported key material"); + } + const identity = { + schema: NATIVE_REVIEW_IDENTITY_SCHEMA, + algorithm: ALGORITHM, + keyId: sha256(publicKey), + publicKey: publicKey.toString("base64") + }; + return { + schema: NATIVE_REVIEW_KEYPAIR_SCHEMA, + identity, + privateKey: privateKey.toString("base64") + }; +} +function nativeReviewIdentityFromPrivateKey(privateKeyValue) { + const privateKey = privateKeyMaterial(privateKeyValue); + const publicKey = createPublicKey(privateKey.key).export({ format: "der", type: "spki" }); + if (!Buffer.isBuffer(publicKey)) { + throw new Error("Native review private key returned unsupported public key material"); + } + return { + schema: NATIVE_REVIEW_IDENTITY_SCHEMA, + algorithm: ALGORITHM, + keyId: sha256(publicKey), + publicKey: publicKey.toString("base64") + }; +} +function signNativeReviewPayloadHash(options) { + const identity = parseNativeReviewIdentity(options.identity); + const hash8 = payloadHash(options.payloadHash); + const privateKey = privateKeyMaterial(options.privateKey); + const derivedPublicKey = createPublicKey(privateKey.key).export({ format: "der", type: "spki" }); + if (!Buffer.isBuffer(derivedPublicKey) || !derivedPublicKey.equals(Buffer.from(identity.publicKey, "base64"))) { + throw new Error("Native review private key does not match the public identity"); + } + return { + schema: NATIVE_REVIEW_SIGNATURE_SCHEMA, + algorithm: ALGORITHM, + keyId: identity.keyId, + payloadHash: hash8, + signature: cryptoSign(null, signaturePayload(hash8), privateKey.key).toString("base64") + }; +} +function verifyNativeReviewPayloadHash(options) { + const identity = parseNativeReviewIdentity(options.identity); + const hash8 = payloadHash(options.payloadHash); + const proof = parseNativeReviewSignature(options.proof); + if (proof.payloadHash !== hash8) { + throw new Error("Native review signature payloadHash does not match the expected payloadHash"); + } + if (proof.keyId !== identity.keyId) { + throw new Error("Native review signature keyId does not match the public identity"); + } + const signature = canonicalBase64( + proof.signature, + "Native review signature", + MAX_SIGNATURE_TEXT, + 64 + ); + const publicKey = publicKeyMaterial(identity.publicKey); + if (!cryptoVerify(null, signaturePayload(hash8), publicKey.key, signature)) { + throw new Error("Native review signature is invalid"); + } + return proof; +} + // domains/comet-native/native-verification-receipt.ts var NATIVE_VERIFICATION_RECEIPT_SCHEMA = "comet.native.verification-receipt.v2"; var NATIVE_WAIVER_RECEIPT_SCHEMA = "comet.native.waiver-receipt.v2"; @@ -18446,9 +17824,9 @@ var IMPLEMENTATION_PAYLOAD_HASH_TAG = "comet.native.implementation-attestation.v var WAIVER_PAYLOAD_HASH_TAG = "comet.native.waiver-attestation.v1"; var REVIEW_MATRIX_HASH_TAG = "comet.native.review-acceptance-matrix.v1"; var REVIEW_EVIDENCE_GRAPH_HASH_TAG = "comet.native.review-evidence-graph.v1"; -var HASH_PATTERN13 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN10 = /^[a-f0-9]{64}$/u; var ACCEPTANCE_ID_PATTERN = /^acceptance-[a-f0-9]{64}$/u; -var CHANGE_NAME_PATTERN6 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; +var CHANGE_NAME_PATTERN4 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var RECEIPT_REF_PATTERN = /^runtime\/evidence\/receipts\/([a-f0-9]{64})\.json$/u; var WAIVER_REF_PATTERN = /^runtime\/evidence\/waivers\/([a-f0-9]{64})\.json$/u; var CHECK_RECEIPT_REF_PATTERN = /^runtime\/evidence\/check-receipts\/([a-f0-9]{64})\.json$/u; @@ -18502,7 +17880,7 @@ function buildNativeReviewEvidenceGraph(input) { graphHash: canonicalHash(REVIEW_EVIDENCE_GRAPH_HASH_TAG, content) }); } -function record9(value, label) { +function record6(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } @@ -18512,7 +17890,7 @@ function record9(value, label) { } return value; } -function exactKeys7(value, expected, label) { +function exactKeys4(value, expected, label) { const actual = Object.keys(value).sort(); const keys = [...expected].sort(); if (actual.length !== keys.length || actual.some((key, index) => key !== keys[index])) { @@ -18520,7 +17898,7 @@ function exactKeys7(value, expected, label) { } } function hash4(value, label) { - if (typeof value !== "string" || !HASH_PATTERN13.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN10.test(value)) { throw new Error(`${label} must be a SHA-256 hash`); } return value; @@ -18579,8 +17957,8 @@ function parseReplayList(value, label) { throw new Error(`${label} must be a bounded array`); } const replays = value.map((entry2, index) => { - const replay = record9(entry2, `${label} ${index}`); - exactKeys7(replay, ["sourceRef", "replayRef"], `${label} ${index}`); + const replay = record6(entry2, `${label} ${index}`); + exactKeys4(replay, ["sourceRef", "replayRef"], `${label} ${index}`); return { sourceRef: receiptRef(replay.sourceRef, `${label} ${index} source`), replayRef: receiptRef(replay.replayRef, `${label} ${index} replay`) @@ -18594,8 +17972,8 @@ function parseReplayList(value, label) { return replays; } function parseNativeReviewEvidenceGraph(value) { - const root = record9(value, "Native review evidence graph"); - exactKeys7( + const root = record6(value, "Native review evidence graph"); + exactKeys4( root, [ "schema", @@ -18638,13 +18016,13 @@ function parseNativeReviewEvidenceGraph(value) { return { ...content, graphHash }; } function bindings(value) { - const root = record9(value, "Native verification receipt bindings"); - exactKeys7( + const root = record6(value, "Native verification receipt bindings"); + exactKeys4( root, ["change", "sourceRevision", "contractHash", "scopeHash", "snapshotHash", "artifactHash"], "Native verification receipt bindings" ); - if (typeof root.change !== "string" || !CHANGE_NAME_PATTERN6.test(root.change) || !Number.isSafeInteger(root.sourceRevision) || root.sourceRevision < 1) { + if (typeof root.change !== "string" || !CHANGE_NAME_PATTERN4.test(root.change) || !Number.isSafeInteger(root.sourceRevision) || root.sourceRevision < 1) { throw new Error("Native verification receipt bindings are invalid"); } return { @@ -18667,8 +18045,8 @@ function parseFindings(value, allowOpenBlocking) { throw new Error("Native independent review findings are invalid"); } const findings = value.map((entry2, index) => { - const finding = record9(entry2, `Native independent review finding ${index}`); - exactKeys7( + const finding = record6(entry2, `Native independent review finding ${index}`); + exactKeys4( finding, ["severity", "status", "summary"], `Native independent review finding ${index}` @@ -18690,9 +18068,9 @@ function parseFindings(value, allowOpenBlocking) { return findings; } function parseEvidence(kind, value, actor, status, role) { - const evidence = record9(value, `Native ${kind} evidence`); + const evidence = record6(value, `Native ${kind} evidence`); if (kind === "automated-check") { - exactKeys7( + exactKeys4( evidence, [ "executable", @@ -18723,8 +18101,8 @@ function parseEvidence(kind, value, actor, status, role) { if (status === "passed" && evidence.exitCode !== 0 || status === "failed" && evidence.exitCode === 0 || status === "passed" && (evidence.timedOut || evidence.signal !== null)) { throw new Error("Native automated-check status does not match its exit code"); } - const worktree = record9(evidence.worktree, "Native automated-check worktree"); - exactKeys7( + const worktree = record6(evidence.worktree, "Native automated-check worktree"); + exactKeys4( worktree, ["provider", "root", "beforeCommit", "afterCommit"], "Native automated-check worktree" @@ -18732,8 +18110,8 @@ function parseEvidence(kind, value, actor, status, role) { if (worktree.provider !== "git" && worktree.provider !== "none" || worktree.beforeCommit !== null && (typeof worktree.beforeCommit !== "string" || !/^[a-f0-9]{40,64}$/u.test(worktree.beforeCommit)) || worktree.afterCommit !== null && (typeof worktree.afterCommit !== "string" || !/^[a-f0-9]{40,64}$/u.test(worktree.afterCommit))) { throw new Error("Native automated-check worktree identity is invalid"); } - const afterFence = record9(evidence.afterFence, "Native automated-check after fence"); - exactKeys7( + const afterFence = record6(evidence.afterFence, "Native automated-check after fence"); + exactKeys4( afterFence, ["snapshotHash", "scopeHash", "matched"], "Native automated-check after fence" @@ -18771,7 +18149,7 @@ function parseEvidence(kind, value, actor, status, role) { }; } if (kind === "static-inspection") { - exactKeys7( + exactKeys4( evidence, ["subjects", "rule", "resultSummary", "checkReceiptRef", "checkReceiptHash"], "Native static-inspection evidence" @@ -18794,7 +18172,7 @@ function parseEvidence(kind, value, actor, status, role) { }; } if (kind === "manual-evidence") { - exactKeys7( + exactKeys4( evidence, ["steps", "observations", "responsible"], "Native manual-evidence evidence" @@ -18808,7 +18186,7 @@ function parseEvidence(kind, value, actor, status, role) { }; } if (kind === "implementation-attestation") { - exactKeys7( + exactKeys4( evidence, ["implementationExecutionId", "reviewPolicyHash", "implementationIdentity", "attestation"], "Native implementation-attestation evidence" @@ -18830,7 +18208,7 @@ function parseEvidence(kind, value, actor, status, role) { attestation: parseNativeReviewSignature(evidence.attestation) }; } - exactKeys7( + exactKeys4( evidence, [ "preparationHash", @@ -18861,8 +18239,8 @@ function parseEvidence(kind, value, actor, status, role) { if (actor !== `review-key:${reviewerIdentity.keyId}`) { throw new Error("Native independent review actor/reviewer identity mismatch"); } - const checked = record9(evidence.checked, "Native independent review checks"); - exactKeys7( + const checked = record6(evidence.checked, "Native independent review checks"); + exactKeys4( checked, [ "acceptanceApplicability", @@ -18904,8 +18282,8 @@ function parseEvidence(kind, value, actor, status, role) { }; } function receiptContent(value) { - const root = record9(value, "Native verification receipt"); - exactKeys7( + const root = record6(value, "Native verification receipt"); + exactKeys4( root, [ "schema", @@ -19001,8 +18379,8 @@ function buildNativeVerificationReceipt(input) { }; } function parseNativeVerificationReceipt(value) { - const root = record9(value, "Native verification receipt"); - exactKeys7( + const root = record6(value, "Native verification receipt"); + exactKeys4( root, [ "schema", @@ -19031,8 +18409,8 @@ function parseNativeVerificationReceipt(value) { return { ...content, receiptHash }; } function waiverContent(value) { - const root = record9(value, "Native waiver receipt"); - exactKeys7( + const root = record6(value, "Native waiver receipt"); + exactKeys4( root, [ "schema", @@ -19081,8 +18459,8 @@ function buildNativeWaiverReceipt(input) { return { ...content, waiverHash: canonicalHash(WAIVER_RECEIPT_HASH_TAG, content) }; } function parseNativeWaiverReceipt(value) { - const root = record9(value, "Native waiver receipt"); - exactKeys7( + const root = record6(value, "Native waiver receipt"); + exactKeys4( root, [ "schema", @@ -19126,12 +18504,12 @@ function nativeBlockedCheckId(receipt) { } // domains/comet-native/native-evidence-storage.ts -var HASH_PATTERN14 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN11 = /^[a-f0-9]{64}$/u; var MAX_NATIVE_EVIDENCE_DOCUMENT_BYTES = MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES; var MAX_NATIVE_IMPLEMENTATION_SCOPE_BUNDLE_BYTES = 3 * MAX_NATIVE_EVIDENCE_DOCUMENT_BYTES; -function isInside7(parent, target) { - const relative = path28.relative(parent, target); - return relative === "" || !path28.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path28.sep}`); +function isInside6(parent, target) { + const relative = path25.relative(parent, target); + return relative === "" || !path25.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path25.sep}`); } function sameDirectoryIdentity5(identity, stat) { return sameFileObject( @@ -19151,30 +18529,30 @@ function sameFileIdentity6(left, right) { return sameFileObject(leftObject, rightObject) && left.birthtimeMs === right.birthtimeMs && left.ctimeMs === right.ctimeMs && left.size === right.size; } async function captureDirectoryIdentity3(directory) { - const stat = await fs17.lstat(directory); + const stat = await fs15.lstat(directory); if (!stat.isDirectory() || stat.isSymbolicLink()) { throw new Error(`Native evidence parent must be a real directory: ${directory}`); } return { path: directory, - realPath: await fs17.realpath(directory), + realPath: await fs15.realpath(directory), dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs }; } async function captureDirectoryChain4(root, directory) { - const lexicalRoot = path28.resolve(root); - const lexicalDirectory = path28.resolve(directory); - if (!isInside7(lexicalRoot, lexicalDirectory)) { + const lexicalRoot = path25.resolve(root); + const lexicalDirectory = path25.resolve(directory); + if (!isInside6(lexicalRoot, lexicalDirectory)) { throw new Error("Native evidence path is outside its change"); } const chain = [await captureDirectoryIdentity3(lexicalRoot)]; let cursor = lexicalRoot; - for (const segment of path28.relative(lexicalRoot, lexicalDirectory).split(path28.sep).filter(Boolean)) { - cursor = path28.join(cursor, segment); + for (const segment of path25.relative(lexicalRoot, lexicalDirectory).split(path25.sep).filter(Boolean)) { + cursor = path25.join(cursor, segment); const identity = await captureDirectoryIdentity3(cursor); - if (!isInside7(chain[0].realPath, identity.realPath)) { + if (!isInside6(chain[0].realPath, identity.realPath)) { throw new Error(`Native evidence parent resolves outside its change: ${cursor}`); } chain.push(identity); @@ -19183,29 +18561,29 @@ async function captureDirectoryChain4(root, directory) { } async function verifyDirectoryChain5(chain) { for (const identity of chain) { - const stat = await fs17.lstat(identity.path); - if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity5(identity, stat) || await fs17.realpath(identity.path) !== identity.realPath) { + const stat = await fs15.lstat(identity.path); + if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity5(identity, stat) || await fs15.realpath(identity.path) !== identity.realPath) { throw new Error(`Native evidence parent changed while reading: ${identity.path}`); } } } async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) { - const chain = await captureDirectoryChain4(changeRoot, path28.dirname(file)); + const chain = await captureDirectoryChain4(changeRoot, path25.dirname(file)); await hooks.afterParentChainCaptured?.(); - const lexical = await fs17.lstat(file); + const lexical = await fs15.lstat(file); if (!lexical.isFile() || lexical.isSymbolicLink()) { throw new Error("Native evidence document must be a regular file"); } - const realPath = await fs17.realpath(file); - if (!isInside7(chain[0].realPath, realPath)) { + const realPath = await fs15.realpath(file); + if (!isInside6(chain[0].realPath, realPath)) { throw new Error("Native evidence document resolves outside its change"); } - const handle = await fs17.open(file, "r"); + const handle = await fs15.open(file, "r"); try { const [opened, pathAfterOpen, realPathAfterOpen] = await Promise.all([ handle.stat(), - fs17.lstat(file), - fs17.realpath(file) + fs15.lstat(file), + fs15.realpath(file) ]); await verifyDirectoryChain5(chain); if (!opened.isFile() || !pathAfterOpen.isFile() || pathAfterOpen.isSymbolicLink() || realPathAfterOpen !== realPath || !sameFileIdentity6(opened, lexical) || !sameFileIdentity6(opened, pathAfterOpen)) { @@ -19235,8 +18613,8 @@ async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) { await hooks.beforeFinalCheck?.(); const [afterHandle, afterPath, afterRealPath] = await Promise.all([ handle.stat(), - fs17.lstat(file), - fs17.realpath(file) + fs15.lstat(file), + fs15.realpath(file) ]); await verifyDirectoryChain5(chain); if (!afterPath.isFile() || afterPath.isSymbolicLink() || afterRealPath !== realPath || !sameFileIdentity6(opened, afterHandle) || !sameFileIdentity6(opened, afterPath)) { @@ -19248,7 +18626,7 @@ async function readBoundedEvidenceJson(file, changeRoot, hooks = {}) { } } function nativeEvidenceRef(kind, hash8) { - if (!HASH_PATTERN14.test(hash8)) throw new Error("Native evidence hash is invalid"); + if (!HASH_PATTERN11.test(hash8)) throw new Error("Native evidence hash is invalid"); return `runtime/evidence/${kind}/${hash8}.json`; } function nativeReportEvidenceRef(hash8) { @@ -19256,7 +18634,7 @@ function nativeReportEvidenceRef(hash8) { } async function writeNativeVerificationReportSnapshot(options) { const encoded = Buffer.from(options.text, "utf8"); - if (encoded.byteLength > MAX_NATIVE_EVIDENCE_DOCUMENT_BYTES || createHash15("sha256").update(encoded).digest("hex") !== options.hash) { + if (encoded.byteLength > MAX_NATIVE_EVIDENCE_DOCUMENT_BYTES || createHash14("sha256").update(encoded).digest("hex") !== options.hash) { throw new Error("Native verification report snapshot hash or size is invalid"); } return writeEvidenceDocument({ @@ -19272,13 +18650,13 @@ async function writeNativeVerificationReportSnapshot(options) { }); } async function readNativeVerificationReportSnapshot(paths, name, hash8) { - if (!HASH_PATTERN14.test(hash8)) throw new Error("Native report evidence hash is invalid"); + if (!HASH_PATTERN11.test(hash8)) throw new Error("Native report evidence hash is invalid"); const value = await readEvidenceDocument({ paths, name, kind: "reports", hash: hash8 }); if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error("Native report evidence must be an object"); } const report = value; - if (Object.keys(report).sort().join(",") !== "content,reportHash,schema" || report.schema !== "comet.native.verification-report.v1" || report.reportHash !== hash8 || typeof report.content !== "string" || createHash15("sha256").update(Buffer.from(report.content, "utf8")).digest("hex") !== hash8) { + if (Object.keys(report).sort().join(",") !== "content,reportHash,schema" || report.schema !== "comet.native.verification-report.v1" || report.reportHash !== hash8 || typeof report.content !== "string" || createHash14("sha256").update(Buffer.from(report.content, "utf8")).digest("hex") !== hash8) { throw new Error("Native report evidence does not match its hash"); } return report.content; @@ -19293,7 +18671,7 @@ function parseEvidenceRef(ref, expectedKind) { return match[2]; } function evidenceFile(paths, name, kind, hash8) { - return path28.join(nativeChangeDir(paths, name), ...nativeEvidenceRef(kind, hash8).split("/")); + return path25.join(nativeChangeDir(paths, name), ...nativeEvidenceRef(kind, hash8).split("/")); } async function readEvidenceDocument(options) { const file = evidenceFile(options.paths, options.name, options.kind, options.hash); @@ -19581,8 +18959,8 @@ async function inspectNativeChangeConflicts(paths, name) { // domains/comet-native/native-transition-journal.ts import { randomUUID as randomUUID3 } from "crypto"; -import { promises as fs18 } from "fs"; -import path29 from "path"; +import { promises as fs16 } from "fs"; +import path26 from "path"; import { isDeepStrictEqual } from "util"; // domains/comet-native/native-repair-stagnation.ts @@ -19598,7 +18976,7 @@ var NATIVE_REPAIR_STAGNATION_LIMITS = { }; var SIGNATURE_HASH_TAG = "comet.native.repair-signature.v1"; var OVERRIDE_SUMMARY_HASH_TAG = "comet.native.repair-override-summary.v1"; -var HASH_PATTERN15 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN12 = /^[a-f0-9]{64}$/u; var TOKEN_PATTERN = /^[a-z0-9][a-z0-9._:/-]{0,255}$/u; function compareText5(left, right) { if (left < right) return -1; @@ -19606,7 +18984,7 @@ function compareText5(left, right) { return 0; } function hash5(value, label) { - if (typeof value !== "string" || !HASH_PATTERN15.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN12.test(value)) { throw new Error(`${label} must be a SHA-256 hash`); } return value; @@ -19674,11 +19052,11 @@ function normalizeHistory(history) { } let previousIteration = 0; let previousRevision = 0; - return history.map((record13, index) => { - if (!record13 || record13.kind !== "failure" && record13.kind !== "override") { + return history.map((record12, index) => { + if (!record12 || record12.kind !== "failure" && record12.kind !== "override") { throw new Error(`Native repair history record ${index} is invalid`); } - const expectedKeys = record13.kind === "failure" ? record13.failedAcceptanceIds === void 0 && record13.failedCheckIds === void 0 ? ["iteration", "kind", "revision", "signatureHash"] : [ + const expectedKeys = record12.kind === "failure" ? record12.failedAcceptanceIds === void 0 && record12.failedCheckIds === void 0 ? ["iteration", "kind", "revision", "signatureHash"] : [ "failedAcceptanceIds", "failedCheckIds", "iteration", @@ -19686,22 +19064,22 @@ function normalizeHistory(history) { "revision", "signatureHash" ] : ["iteration", "kind", "revision", "signatureHash", "summaryHash"]; - const keys = Object.keys(record13).sort(compareText5); + const keys = Object.keys(record12).sort(compareText5); if (keys.length !== expectedKeys.length || keys.some((key, keyIndex) => key !== expectedKeys[keyIndex])) { throw new Error(`Native repair history record ${index} fields are invalid`); } - const iteration = positiveInteger4(record13.iteration, `Native repair history ${index} iteration`); - const revision = positiveInteger4(record13.revision, `Native repair history ${index} revision`); + const iteration = positiveInteger4(record12.iteration, `Native repair history ${index} iteration`); + const revision = positiveInteger4(record12.revision, `Native repair history ${index} revision`); const previous = index > 0 ? history[index - 1] : null; - const pairedOverride = record13.kind === "override" && previous?.kind === "failure" && iteration === previousIteration && revision >= previousRevision && record13.signatureHash === previous.signatureHash; + const pairedOverride = record12.kind === "override" && previous?.kind === "failure" && iteration === previousIteration && revision >= previousRevision && record12.signatureHash === previous.signatureHash; if (!pairedOverride && (iteration <= previousIteration || revision <= previousRevision)) { throw new Error("Native repair history must be strictly ordered by revision and iteration"); } previousIteration = iteration; previousRevision = revision; - const signatureHash = hash5(record13.signatureHash, `Native repair history ${index} signature`); - if (record13.kind === "failure") { - if (record13.failedAcceptanceIds === void 0 && record13.failedCheckIds === void 0) { + const signatureHash = hash5(record12.signatureHash, `Native repair history ${index} signature`); + if (record12.kind === "failure") { + if (record12.failedAcceptanceIds === void 0 && record12.failedCheckIds === void 0) { return { kind: "failure", iteration, revision, signatureHash }; } return { @@ -19710,13 +19088,13 @@ function normalizeHistory(history) { revision, signatureHash, failedAcceptanceIds: normalizedTokens( - record13.failedAcceptanceIds ?? [], + record12.failedAcceptanceIds ?? [], `Native repair history ${index} failed acceptance IDs`, NATIVE_REPAIR_STAGNATION_LIMITS.maxFailedAcceptanceIds, true ), failedCheckIds: normalizedTokens( - record13.failedCheckIds ?? [], + record12.failedCheckIds ?? [], `Native repair history ${index} failed check IDs`, NATIVE_REPAIR_STAGNATION_LIMITS.maxFailedCheckIds, true @@ -19728,7 +19106,7 @@ function normalizeHistory(history) { iteration, revision, signatureHash, - summaryHash: hash5(record13.summaryHash, `Native repair history ${index} summary`) + summaryHash: hash5(record12.summaryHash, `Native repair history ${index} summary`) }; }); } @@ -19769,7 +19147,7 @@ function decideNativeRepairStagnation(options) { const signature = buildNativeRepairSignature(options.facts); const history = normalizeHistory(options.history); const failures = history.filter( - (record13) => record13.kind === "failure" + (record12) => record12.kind === "failure" ); const totalRepairFailures = failures.length + 1; const maxVerifyFailures = positiveInteger4( @@ -19828,7 +19206,7 @@ function decideNativeRepairStagnation(options) { } overrideSummary(options.override.summary); const alreadyUsed2 = history.some( - (record13) => record13.kind === "override" && record13.signatureHash === signature.signatureHash + (record12) => record12.kind === "override" && record12.signatureHash === signature.signatureHash ); return { disposition: alreadyUsed2 ? "manual-stop" : "continue", @@ -19841,7 +19219,7 @@ function decideNativeRepairStagnation(options) { }; } const alreadyUsed = history.some( - (record13) => record13.kind === "override" && record13.signatureHash === signature.signatureHash + (record12) => record12.kind === "override" && record12.signatureHash === signature.signatureHash ); return { disposition: "manual-stop", @@ -19879,7 +19257,7 @@ var NATIVE_REPAIR_TRAJECTORY_LIMITS = { maxTotalDataCharacters: 1048576, maxRunIdCharacters: 256 }; -var HASH_PATTERN16 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN13 = /^[a-f0-9]{64}$/u; var REPAIR_SCOPE_HASH_TAG = "comet.native.repair-scope.v1"; var EVENT_TYPES2 = /* @__PURE__ */ new Set([ "run_started", @@ -19909,13 +19287,13 @@ function compareText6(left, right) { if (left > right) return 1; return 0; } -function exactKeys8(value, expected, label) { +function exactKeys5(value, expected, label) { const actual = Object.keys(value).sort(compareText6); if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) { throw new Error(`${label} fields are invalid`); } } -function record10(value, label) { +function record7(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } @@ -19926,7 +19304,7 @@ function record10(value, label) { return value; } function hash6(value, label) { - if (typeof value !== "string" || !HASH_PATTERN16.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN13.test(value)) { throw new Error(`${label} must be a SHA-256 hash`); } return value; @@ -19992,7 +19370,7 @@ function boundedData(value, depth, budget, label, legacyTransitionText = false) value.forEach((entry2, index) => boundedData(entry2, depth + 1, budget, `${label}[${index}]`)); return; } - const object2 = record10(value, label); + const object2 = record7(value, label); const keys = Object.keys(object2); if (keys.length > NATIVE_REPAIR_TRAJECTORY_LIMITS.maxObjectFields) { throw new Error(`${label} contains too many fields`); @@ -20028,8 +19406,8 @@ function parseTimestamp(value) { return value; } function parseEvent2(value, index, runId) { - const event = record10(value, `Native repair trajectory event ${index + 1}`); - exactKeys8(event, EVENT_KEYS2, `Native repair trajectory event ${index + 1}`); + const event = record7(value, `Native repair trajectory event ${index + 1}`); + exactKeys5(event, EVENT_KEYS2, `Native repair trajectory event ${index + 1}`); if (!Number.isSafeInteger(event.sequence) || event.sequence !== index + 1) { throw new Error("Native repair trajectory sequence is invalid"); } @@ -20042,7 +19420,7 @@ function parseEvent2(value, index, runId) { } const budget = { nodes: 0, characters: 0, ancestors: /* @__PURE__ */ new Set() }; boundedData(event.data, 0, budget, "Native trajectory event data"); - record10(event.data, "Native repair trajectory event data"); + record7(event.data, "Native repair trajectory event data"); return { event, dataNodes: budget.nodes, @@ -20050,11 +19428,11 @@ function parseEvent2(value, index, runId) { }; } function parseNativeRepairTrajectoryProjection(value) { - const projection = record10(value, "Native repair trajectory projection"); + const projection = record7(value, "Native repair trajectory projection"); const keys = Object.keys(projection).sort(compareText6); const legacy = keys.length === LEGACY_PROJECTION_KEYS.length && keys.every((key, index) => key === LEGACY_PROJECTION_KEYS[index]); if (!legacy) { - exactKeys8(projection, PROJECTION_KEYS, "Native repair trajectory projection"); + exactKeys5(projection, PROJECTION_KEYS, "Native repair trajectory projection"); } const signatureHash = hash6(projection.signatureHash, "Native repair trajectory signature hash"); if (projection.disposition !== "continue" && projection.disposition !== "warn" && projection.disposition !== "manual-stop" && projection.disposition !== "hard-stop") { @@ -20243,8 +19621,8 @@ function inspectLatestNativeRepairProjection(options) { } function acceptLatestNativeRepairOverride(options) { const projected = projectNativeRepairHistory(options); - const request = record10(options.override, "Native repair override request"); - exactKeys8(request, ["expectedSignatureHash", "summary"], "Native repair override request"); + const request = record7(options.override, "Native repair override request"); + exactKeys5(request, ["expectedSignatureHash", "summary"], "Native repair override request"); const expectedSignatureHash = hash6( request.expectedSignatureHash, "Native repair override expected signature" @@ -20497,7 +19875,7 @@ var NativeTransitionMigrationRequiredError = class extends Error { code = "native-transition-migration-required"; }; function nativeTransitionJournalFile(paths, name) { - return path29.join(nativeChangeDir(paths, name), "runtime", "transition.json"); + return path26.join(nativeChangeDir(paths, name), "runtime", "transition.json"); } function nativeTransitionLockName(name) { return `transition-${name}`; @@ -20537,70 +19915,70 @@ function parseTransitionEventData(value, evidenceHash) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error("Native transition journal event data is invalid"); } - const record13 = value; - const keys = Object.keys(record13); + const record12 = value; + const keys = Object.keys(record12); const unknown = keys.find((key) => !TRANSITION_EVENT_DATA_KEYS.has(key)); const missing = [...REQUIRED_TRANSITION_EVENT_DATA_KEYS].find( - (key) => !Object.hasOwn(record13, key) + (key) => !Object.hasOwn(record12, key) ); - const expectedSize = REQUIRED_TRANSITION_EVENT_DATA_KEYS.size + (Object.hasOwn(record13, "implementationScopeHash") ? 1 : 0) + (Object.hasOwn(record13, "repairScopeHash") ? 1 : 0) + (Object.hasOwn(record13, "repairStagnation") ? 1 : 0); + const expectedSize = REQUIRED_TRANSITION_EVENT_DATA_KEYS.size + (Object.hasOwn(record12, "implementationScopeHash") ? 1 : 0) + (Object.hasOwn(record12, "repairScopeHash") ? 1 : 0) + (Object.hasOwn(record12, "repairStagnation") ? 1 : 0); if (unknown || missing || keys.length !== expectedSize) { throw new Error( `Native transition journal event data keys are invalid${unknown ? `: ${unknown}` : missing ? `: missing ${missing}` : ""}` ); } - if (!["shape", "build", "verify", "archive"].includes(record13.previousPhase) || !["shape", "build", "verify", "archive"].includes(record13.nextPhase)) { + if (!["shape", "build", "verify", "archive"].includes(record12.previousPhase) || !["shape", "build", "verify", "archive"].includes(record12.nextPhase)) { throw new Error("Native transition journal event phases are invalid"); } - if (record13.evidenceHash !== evidenceHash) { + if (record12.evidenceHash !== evidenceHash) { throw new Error("Native transition journal event evidence hash mismatch"); } - assertNativeTrajectoryText(record13.summary, "Native transition journal event summary"); - if (redactNativeCredentialText(record13.summary) !== record13.summary) { + assertNativeTrajectoryText(record12.summary, "Native transition journal event summary"); + if (redactNativeCredentialText(record12.summary) !== record12.summary) { throw new Error("Native transition journal event summary contains unredacted credentials"); } - if (!Array.isArray(record13.artifacts) || record13.artifacts.length > 128 || record13.artifacts.some( + if (!Array.isArray(record12.artifacts) || record12.artifacts.length > 128 || record12.artifacts.some( (artifact) => typeof artifact !== "string" || artifact.length === 0 || Buffer.byteLength(artifact, "utf8") > 512 )) { throw new Error("Native transition journal event artifacts are invalid"); } - if (record13.noCodeReason !== null) { + if (record12.noCodeReason !== null) { assertNativeTrajectoryText( - record13.noCodeReason, + record12.noCodeReason, "Native transition journal event no-code reason" ); - if (redactNativeCredentialText(record13.noCodeReason) !== record13.noCodeReason) { + if (redactNativeCredentialText(record12.noCodeReason) !== record12.noCodeReason) { throw new Error( "Native transition journal event no-code reason contains unredacted credentials" ); } } - if (record13.verificationResult !== null && record13.verificationResult !== "pass" && record13.verificationResult !== "fail") { + if (record12.verificationResult !== null && record12.verificationResult !== "pass" && record12.verificationResult !== "fail") { throw new Error("Native transition journal event verification result is invalid"); } - const implementationScopeHash = Object.hasOwn(record13, "implementationScopeHash") ? record13.implementationScopeHash : null; - if (implementationScopeHash !== null && (typeof implementationScopeHash !== "string" || !/^[a-f0-9]{64}$/.test(implementationScopeHash) || record13.previousPhase !== "build" && record13.previousPhase !== "verify")) { + const implementationScopeHash = Object.hasOwn(record12, "implementationScopeHash") ? record12.implementationScopeHash : null; + if (implementationScopeHash !== null && (typeof implementationScopeHash !== "string" || !/^[a-f0-9]{64}$/.test(implementationScopeHash) || record12.previousPhase !== "build" && record12.previousPhase !== "verify")) { throw new Error("Native transition journal implementation scope hash is invalid"); } - const repairStagnation = Object.hasOwn(record13, "repairStagnation") ? parseNativeRepairTrajectoryProjection(record13.repairStagnation) : null; + const repairStagnation = Object.hasOwn(record12, "repairStagnation") ? parseNativeRepairTrajectoryProjection(record12.repairStagnation) : null; if (repairStagnation) { const failureProjection = repairStagnation.overrideSummaryHash === null; - if (failureProjection && (record13.previousPhase !== "verify" || record13.nextPhase !== "build" || record13.verificationResult !== "fail") || !failureProjection && (record13.previousPhase !== "build" || record13.nextPhase !== "verify" || record13.verificationResult !== null)) { + if (failureProjection && (record12.previousPhase !== "verify" || record12.nextPhase !== "build" || record12.verificationResult !== "fail") || !failureProjection && (record12.previousPhase !== "build" || record12.nextPhase !== "verify" || record12.verificationResult !== null)) { throw new Error("Native transition journal repair projection does not match its phase"); } } - const repairScopeHash = Object.hasOwn(record13, "repairScopeHash") ? record13.repairScopeHash : null; - if (repairScopeHash !== null && (typeof repairScopeHash !== "string" || !/^[a-f0-9]{64}$/.test(repairScopeHash) || record13.previousPhase !== "build" && record13.previousPhase !== "verify" || implementationScopeHash === null)) { + const repairScopeHash = Object.hasOwn(record12, "repairScopeHash") ? record12.repairScopeHash : null; + if (repairScopeHash !== null && (typeof repairScopeHash !== "string" || !/^[a-f0-9]{64}$/.test(repairScopeHash) || record12.previousPhase !== "build" && record12.previousPhase !== "verify" || implementationScopeHash === null)) { throw new Error("Native transition journal repair scope hash is invalid"); } return { - previousPhase: record13.previousPhase, - nextPhase: record13.nextPhase, + previousPhase: record12.previousPhase, + nextPhase: record12.nextPhase, evidenceHash, - summary: record13.summary, - artifacts: [...record13.artifacts], - noCodeReason: record13.noCodeReason, - verificationResult: record13.verificationResult, + summary: record12.summary, + artifacts: [...record12.artifacts], + noCodeReason: record12.noCodeReason, + verificationResult: record12.verificationResult, ...implementationScopeHash ? { implementationScopeHash } : {}, ...repairScopeHash ? { repairScopeHash } : {}, ...repairStagnation ? { repairStagnation } : {} @@ -20608,8 +19986,8 @@ function parseTransitionEventData(value, evidenceHash) { } function parseLegacyTransitionEventData(value, evidenceHash) { if (value && typeof value === "object" && !Array.isArray(value)) { - const record13 = value; - const keys = Object.keys(record13); + const record12 = value; + const keys = Object.keys(record12); const legacyRebaseKeys = /* @__PURE__ */ new Set([ "previousPhase", "nextPhase", @@ -20617,13 +19995,13 @@ function parseLegacyTransitionEventData(value, evidenceHash) { "summary", "reason" ]); - if (keys.length === legacyRebaseKeys.size && keys.every((key) => legacyRebaseKeys.has(key)) && record13.reason === "spec-rebase") { + if (keys.length === legacyRebaseKeys.size && keys.every((key) => legacyRebaseKeys.has(key)) && record12.reason === "spec-rebase") { return parseTransitionEventData( { - previousPhase: record13.previousPhase, - nextPhase: record13.nextPhase, - evidenceHash: record13.evidenceHash, - summary: record13.summary, + previousPhase: record12.previousPhase, + nextPhase: record12.nextPhase, + evidenceHash: record12.evidenceHash, + summary: record12.summary, artifacts: [], noCodeReason: null, verificationResult: null @@ -20677,1109 +20055,1480 @@ function runInvariantProjection(run) { retries: run.retries }; } -function assertCommittableRun(run, label) { - if (run.status !== "running") { - throw new Error(`Native transition journal ${label} status must be running`); +function assertCommittableRun(run, label) { + if (run.status !== "running") { + throw new Error(`Native transition journal ${label} status must be running`); + } + if (run.pending !== null) { + throw new Error(`Native transition journal ${label} pending action must be null`); + } +} +function validateTransitionRunSemantics(previousState, nextState, envelope, allowCompatibleLegacyIdentity = false) { + const { previousRun, nextRun } = envelope; + assertNativeRunMetadata( + nextRun, + "next Run", + allowCompatibleLegacyIdentity || previousRun !== null + ); + assertCommittableRun(nextRun, "next Run"); + if (nextRun.runId !== nextState.run_id || nextRun.currentStep !== nextState.phase) { + throw new Error("Native transition journal next Run does not match the next change state"); + } + if (previousRun === null) { + if (previousState.run_id !== null || previousState.phase !== "shape" || nextRun.iteration !== 1 || Object.keys(nextRun.retries).length !== 0) { + throw new Error("Native transition journal first Run transition is invalid"); + } + return; + } + assertNativeRunMetadata(previousRun, "previous Run", true); + assertCommittableRun(previousRun, "previous Run"); + if (previousState.run_id !== previousRun.runId || previousRun.currentStep !== previousState.phase || nextState.run_id !== previousRun.runId) { + throw new Error("Native transition journal previous Run does not match the change state"); + } + if (nextRun.iteration !== previousRun.iteration + 1) { + throw new Error("Native transition journal next Run iteration must advance exactly once"); + } + if (!isDeepStrictEqual(runInvariantProjection(previousRun), runInvariantProjection(nextRun))) { + throw new Error("Native transition journal Run identity or storage invariants changed"); + } +} +function specRebaseEvidenceHash(name, summary, specChanges) { + return sha256Text( + JSON.stringify({ + operation: "spec-rebase", + change: name, + summary, + specChanges + }) + ); +} +function inferredLegacyTransitionOperation(previousState, nextState, event) { + return previousState.phase !== "shape" && nextState.phase === "build" && event.verificationResult === null ? "spec-rebase" : "advance"; +} +function currentStateRefs(state) { + return "implementation_scope" in state ? { + implementation_scope: state.implementation_scope, + verification_evidence: state.verification_evidence, + partial_allowance: state.partial_allowance + } : null; +} +function validateTransitionStateSemantics(previousState, nextState, envelope, operation, legacyOperation = false) { + const event = envelope.eventData; + const previousRefs = currentStateRefs(previousState); + const nextRefs = currentStateRefs(nextState); + if (event.previousPhase !== previousState.phase || event.nextPhase !== nextState.phase) { + throw new Error("Native transition journal event phases do not match its states"); + } + if (previousState.archived || nextState.archived) { + throw new Error("Native transition journal cannot mutate an archived change"); + } + if (operation === "evidence-retreat") { + const sameIdentity3 = previousState.name === nextState.name && previousState.language === nextState.language && previousState.brief === nextState.brief && previousState.approval === nextState.approval && ("approved_contract_hash" in previousState ? "approved_contract_hash" in nextState && previousState.approved_contract_hash === nextState.approved_contract_hash : !("approved_contract_hash" in nextState)) && previousState.created_at === nextState.created_at && previousState.run_id === nextState.run_id && isDeepStrictEqual(previousState.spec_changes, nextState.spec_changes); + const expectedHash = nativeAdvanceEvidenceHash({ summary: event.summary }); + const validSourcePhase = previousState.phase === "archive" && previousState.verification_result === "pass" || previousState.phase === "verify" && previousState.verification_result === "pending"; + if (!validSourcePhase || nextState.phase !== "build" || nextState.verification_result !== "pending" || nextState.verification_report !== null || event.verificationResult !== null || event.artifacts.length !== 0 || event.noCodeReason !== null || envelope.evidenceHash !== expectedHash || !sameIdentity3 || previousRefs === null || nextRefs === null || nextRefs.implementation_scope !== null || nextRefs.verification_evidence !== null || nextRefs.partial_allowance !== null) { + throw new Error("Native transition journal evidence retreat semantics are invalid"); + } + return; + } + if (operation === "spec-rebase") { + const currentHash = specRebaseEvidenceHash( + previousState.name, + event.summary, + nextState.spec_changes + ); + const legacyHash = sha256Text(`spec-rebase:${previousState.name}:${event.summary}`); + if (previousState.phase === "shape" || nextState.phase !== "build" || event.verificationResult !== null || event.artifacts.length !== 0 || event.noCodeReason !== null || nextState.verification_result !== "pending" || nextState.verification_report !== null || !legacyOperation && envelope.evidenceHash !== currentHash || legacyOperation && envelope.evidenceHash !== currentHash && envelope.evidenceHash !== legacyHash || "implementation_scope" in nextState && (nextState.implementation_scope !== null || nextState.verification_evidence !== null || nextState.partial_allowance !== null)) { + throw new Error("Native transition journal spec rebase semantics are invalid"); + } + return; + } + if (previousState.phase === "shape") { + if (nextState.phase !== "build" || event.verificationResult !== null || event.artifacts.length !== 0 || event.noCodeReason !== null || previousState.verification_result !== "pending" || nextState.verification_result !== "pending" || previousState.verification_report !== null || nextState.verification_report !== null || previousRefs !== null && nextRefs !== null && (previousRefs.implementation_scope !== null || previousRefs.verification_evidence !== null || previousRefs.partial_allowance !== null || nextRefs.implementation_scope !== null || nextRefs.verification_evidence !== null || nextRefs.partial_allowance !== null)) { + throw new Error("Native transition journal Shape to Build semantics are invalid"); + } + return; + } + if (previousState.phase === "build") { + if (nextState.phase !== "verify" || event.verificationResult !== null || event.artifacts.length === 0 && event.noCodeReason === null || nextState.verification_result !== "pending" || nextState.verification_report !== null || previousRefs !== null && nextRefs !== null && (nextRefs.implementation_scope === null || nextRefs.verification_evidence !== null || nextRefs.implementation_scope !== previousRefs.implementation_scope && nextRefs.partial_allowance !== null && nextRefs.partial_allowance === previousRefs.partial_allowance)) { + throw new Error("Native transition journal Build to Verify semantics are invalid"); + } + return; + } + if (previousState.phase === "verify") { + const expectedNext = event.verificationResult === "pass" ? "archive" : "build"; + if (event.verificationResult !== "pass" && event.verificationResult !== "fail" || nextState.phase !== expectedNext || event.artifacts.length !== 0 || event.noCodeReason !== null || nextState.verification_result !== event.verificationResult || nextState.verification_report === null || previousRefs !== null && nextRefs !== null && (previousRefs.implementation_scope === null || nextRefs.implementation_scope !== previousRefs.implementation_scope || nextRefs.partial_allowance !== previousRefs.partial_allowance || previousRefs.verification_evidence !== null || nextRefs.verification_evidence === null)) { + throw new Error("Native transition journal Verify outcome semantics are invalid"); + } + return; + } + throw new Error("Native transition journal cannot advance from Archive"); +} +function parseNativeTransitionJournalValue(value, expectedName) { + const journal = journalRecord(value); + rejectUnknownJournalFields(journal, CURRENT_JOURNAL_KEYS); + if (journal.schema !== NATIVE_TRANSITION_SCHEMA) { + throw new Error(`Expected Native transition schema ${NATIVE_TRANSITION_SCHEMA}`); + } + const minimumRuntimeVersion = positiveInteger5( + journal.minimum_runtime_version, + "Native transition minimum_runtime_version" + ); + if (minimumRuntimeVersion > NATIVE_RUNTIME_PROTOCOL_VERSION) { + throw new Error( + `Native transition requires runtime protocol ${minimumRuntimeVersion}; current protocol is ${NATIVE_RUNTIME_PROTOCOL_VERSION}` + ); + } + if (minimumRuntimeVersion !== NATIVE_RUNTIME_PROTOCOL_VERSION) { + throw new Error( + `Native transition ${NATIVE_TRANSITION_SCHEMA} minimum_runtime_version must be ${NATIVE_RUNTIME_PROTOCOL_VERSION}` + ); + } + const revision = positiveInteger5(journal.revision, "Native transition revision"); + if (revision !== 1) throw new Error("Native transition journal revision must be 1"); + if (journal.operation !== "advance" && journal.operation !== "spec-rebase" && journal.operation !== "evidence-retreat") { + throw new Error("Native transition journal operation is invalid"); + } + const envelope = validateJournalEnvelope(journal, expectedName); + const previousState = parseNativeChangeValue(journal.previousState); + const nextState = parseNativeChangeValue(journal.nextState); + if (previousState.name !== expectedName || nextState.name !== expectedName) { + throw new Error("Native transition journal state mismatch"); + } + validateTransitionStateSemantics(previousState, nextState, envelope, journal.operation); + validateTransitionRunSemantics(previousState, nextState, envelope); + if (nextState.revision !== previousState.revision + 1) { + throw new Error("Native transition journal state revision must advance exactly once"); + } + return { + schema: NATIVE_TRANSITION_SCHEMA, + minimum_runtime_version: NATIVE_RUNTIME_PROTOCOL_VERSION, + revision, + operation: journal.operation, + id: envelope.id, + change: expectedName, + evidenceHash: envelope.evidenceHash, + createdAt: envelope.createdAt, + previousState, + nextState, + previousRun: envelope.previousRun, + nextRun: envelope.nextRun, + eventData: envelope.eventData + }; +} +function parseLegacyNativeTransitionJournalValue(value, expectedName) { + const journal = journalRecord(value); + rejectUnknownJournalFields(journal, LEGACY_JOURNAL_KEYS); + if (journal.schema !== NATIVE_LEGACY_TRANSITION_SCHEMA) { + throw new Error(`Expected Native transition schema ${NATIVE_LEGACY_TRANSITION_SCHEMA}`); + } + const envelope = validateJournalEnvelope(journal, expectedName, true); + const previousState = parseLegacyNativeChangeValue(journal.previousState); + const nextState = parseLegacyNativeChangeValue(journal.nextState); + if (previousState.name !== expectedName || nextState.name !== expectedName) { + throw new Error("Native transition journal state mismatch"); + } + validateTransitionStateSemantics( + previousState, + nextState, + envelope, + inferredLegacyTransitionOperation(previousState, nextState, envelope.eventData), + true + ); + validateTransitionRunSemantics(previousState, nextState, envelope, true); + return { + schema: NATIVE_LEGACY_TRANSITION_SCHEMA, + id: envelope.id, + change: expectedName, + evidenceHash: envelope.evidenceHash, + createdAt: envelope.createdAt, + previousState, + nextState, + previousRun: envelope.previousRun, + nextRun: envelope.nextRun, + eventData: envelope.eventData + }; +} +function parseV2NativeTransitionJournalValue(value, expectedName) { + const journal = journalRecord(value); + rejectUnknownJournalFields(journal, V2_JOURNAL_KEYS); + if (journal.schema !== NATIVE_V2_TRANSITION_SCHEMA) { + throw new Error(`Expected Native transition schema ${NATIVE_V2_TRANSITION_SCHEMA}`); + } + const minimumRuntimeVersion = positiveInteger5( + journal.minimum_runtime_version, + "Native v2 transition minimum_runtime_version" + ); + if (minimumRuntimeVersion !== 2) { + throw new Error( + `Native transition ${NATIVE_V2_TRANSITION_SCHEMA} minimum_runtime_version must be 2` + ); + } + const revision = positiveInteger5(journal.revision, "Native v2 transition revision"); + if (revision !== 1) throw new Error("Native v2 transition journal revision must be 1"); + const envelope = validateJournalEnvelope(journal, expectedName, true); + const previousState = parseV2NativeChangeValue(journal.previousState); + const nextState = parseV2NativeChangeValue(journal.nextState); + if (previousState.name !== expectedName || nextState.name !== expectedName) { + throw new Error("Native v2 transition journal state mismatch"); + } + validateTransitionStateSemantics( + previousState, + nextState, + envelope, + inferredLegacyTransitionOperation(previousState, nextState, envelope.eventData), + true + ); + validateTransitionRunSemantics(previousState, nextState, envelope, true); + if (nextState.revision !== previousState.revision + 1) { + throw new Error("Native v2 transition journal state revision must advance exactly once"); + } + return { + schema: NATIVE_V2_TRANSITION_SCHEMA, + minimum_runtime_version: 2, + revision, + id: envelope.id, + change: expectedName, + evidenceHash: envelope.evidenceHash, + createdAt: envelope.createdAt, + previousState, + nextState, + previousRun: envelope.previousRun, + nextRun: envelope.nextRun, + eventData: envelope.eventData + }; +} +function inspectNativeTransitionJournalValue(value, expectedName) { + const journal = journalRecord(value); + if (journal.schema === NATIVE_TRANSITION_SCHEMA) { + return { status: "current", journal: parseNativeTransitionJournalValue(journal, expectedName) }; } - if (run.pending !== null) { - throw new Error(`Native transition journal ${label} pending action must be null`); + if (journal.schema === NATIVE_LEGACY_TRANSITION_SCHEMA) { + return { + status: "migration-required", + journal: parseLegacyNativeTransitionJournalValue(journal, expectedName) + }; + } + if (journal.schema === NATIVE_V2_TRANSITION_SCHEMA) { + return { + status: "migration-required", + journal: parseV2NativeTransitionJournalValue(journal, expectedName) + }; } + throw new Error(`Unsupported Native transition journal schema: ${String(journal.schema)}`); } -function validateTransitionRunSemantics(previousState, nextState, envelope, allowCompatibleLegacyIdentity = false) { - const { previousRun, nextRun } = envelope; - assertNativeRunMetadata( - nextRun, - "next Run", - allowCompatibleLegacyIdentity || previousRun !== null - ); - assertCommittableRun(nextRun, "next Run"); - if (nextRun.runId !== nextState.run_id || nextRun.currentStep !== nextState.phase) { - throw new Error("Native transition journal next Run does not match the next change state"); +async function inspectPendingNativeTransitionSchema(paths, name) { + const file = nativeTransitionJournalFile(paths, name); + await resolveContainedNativePath(paths.nativeRoot, file); + try { + const snapshot2 = await readNativeProtectedFile({ + root: paths.nativeRoot, + file, + maxBytes: NATIVE_TRANSITION_JOURNAL_MAX_BYTES, + label: `Native transition journal ${name}` + }); + return inspectNativeTransitionJournalValue(JSON.parse(snapshot2.bytes.toString("utf8")), name); + } catch (error) { + if (error.code === "ENOENT") return null; + throw error; } - if (previousRun === null) { - if (previousState.run_id !== null || previousState.phase !== "shape" || nextRun.iteration !== 1 || Object.keys(nextRun.retries).length !== 0) { - throw new Error("Native transition journal first Run transition is invalid"); - } - return; +} +async function inspectPendingNativeTransition(paths, name) { + const inspection = await inspectPendingNativeTransitionSchema(paths, name); + if (!inspection) return null; + if (inspection.status === "migration-required") { + throw new NativeTransitionMigrationRequiredError(name); } - assertNativeRunMetadata(previousRun, "previous Run", true); - assertCommittableRun(previousRun, "previous Run"); - if (previousState.run_id !== previousRun.runId || previousRun.currentStep !== previousState.phase || nextState.run_id !== previousRun.runId) { - throw new Error("Native transition journal previous Run does not match the change state"); + return inspection.journal; +} +async function prepareNativeTransition(options) { + if (await hasPendingNativeSchemaMigration(options.paths, options.nextState.name)) { + throw new Error( + `Native schema migration is incomplete for ${options.nextState.name}; run doctor --repair` + ); } - if (nextRun.iteration !== previousRun.iteration + 1) { - throw new Error("Native transition journal next Run iteration must advance exactly once"); + await assertNativeTrajectoryHealthy(options.paths, options.nextState.name); + const journal = { + schema: NATIVE_TRANSITION_SCHEMA, + minimum_runtime_version: NATIVE_RUNTIME_PROTOCOL_VERSION, + revision: 1, + operation: options.operation ?? "advance", + id: options.transitionId?.() ?? randomUUID3(), + change: options.nextState.name, + evidenceHash: options.evidenceHash, + createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(), + previousState: options.previousState, + nextState: options.nextState, + previousRun: options.previousRun, + nextRun: options.nextRun, + eventData: options.eventData + }; + const validated = parseNativeTransitionJournalValue(journal, journal.change); + const file = nativeTransitionJournalFile(options.paths, validated.change); + await resolveContainedNativePath(options.paths.nativeRoot, file); + if (await inspectPendingNativeTransition(options.paths, validated.change)) { + throw new Error(`Native transition recovery is already pending for ${validated.change}`); } - if (!isDeepStrictEqual(runInvariantProjection(previousRun), runInvariantProjection(nextRun))) { - throw new Error("Native transition journal Run identity or storage invariants changed"); + await atomicWriteJson(file, validated); + return validated; +} +function assertRunRecoverySource(actual, journal) { + if (sameValue(actual, journal.nextRun)) return "next"; + if (journal.previousRun === null) { + if (actual === null) return "previous"; + } else if (sameValue(actual, journal.previousRun)) { + return "previous"; } + throw new Error( + `Native transition Run content changed for ${journal.change}; recovery journal was preserved` + ); } -function specRebaseEvidenceHash(name, summary, specChanges) { - return sha256Text( - JSON.stringify({ - operation: "spec-rebase", - change: name, - summary, - specChanges - }) +function assertChangeRecoverySource(actual, journal) { + if (sameValue(actual, journal.nextState)) return "next"; + if (sameValue(actual, journal.previousState)) return "previous"; + throw new Error( + `Native transition change content changed for ${journal.change}; recovery journal was preserved` ); } -function inferredLegacyTransitionOperation(previousState, nextState, event) { - return previousState.phase !== "shape" && nextState.phase === "build" && event.verificationResult === null ? "spec-rebase" : "advance"; +function expectedTrajectoryEvent(options) { + const { journal } = options; + return trajectoryEventForComparison({ + sequence: options.sequence, + timestamp: journal.createdAt, + type: options.type, + runId: journal.nextRun.runId, + data: options.type === "run_started" ? { + runtime: "comet-native", + phase: journal.previousState.phase, + transitionId: journal.id + } : { ...journal.eventData, transitionId: journal.id } + }); } -function currentStateRefs(state) { - return "implementation_scope" in state ? { - implementation_scope: state.implementation_scope, - verification_evidence: state.verification_evidence, - partial_allowance: state.partial_allowance - } : null; +function trajectoryEventForComparison(event) { + return { + sequence: event.sequence, + timestamp: event.timestamp, + type: event.type, + runId: event.runId, + data: event.data + }; } -function validateTransitionStateSemantics(previousState, nextState, envelope, operation, legacyOperation = false) { - const event = envelope.eventData; - const previousRefs = currentStateRefs(previousState); - const nextRefs = currentStateRefs(nextState); - if (event.previousPhase !== previousState.phase || event.nextPhase !== nextState.phase) { - throw new Error("Native transition journal event phases do not match its states"); - } - if (previousState.archived || nextState.archived) { - throw new Error("Native transition journal cannot mutate an archived change"); +function inspectExistingTransitionEvents(trajectory, journal) { + const collisions = trajectory.map((event, index) => ({ event, index })).filter(({ event }) => event.data.transitionId === journal.id); + const started = collisions.filter(({ event }) => event.type === "run_started"); + const transitioned = collisions.filter(({ event }) => event.type === "state_transitioned"); + if (collisions.length !== started.length + transitioned.length || started.length > (journal.previousRun === null ? 1 : 0) || transitioned.length > 1 || journal.previousRun === null && transitioned.length === 1 && (started.length !== 1 || started[0].index >= transitioned[0].index)) { + throw new Error( + `Native trajectory transition id collision for ${journal.change}; recovery journal was preserved` + ); } - if (operation === "evidence-retreat") { - const sameIdentity3 = previousState.name === nextState.name && previousState.language === nextState.language && previousState.brief === nextState.brief && previousState.approval === nextState.approval && ("approved_contract_hash" in previousState ? "approved_contract_hash" in nextState && previousState.approved_contract_hash === nextState.approved_contract_hash : !("approved_contract_hash" in nextState)) && previousState.created_at === nextState.created_at && previousState.run_id === nextState.run_id && isDeepStrictEqual(previousState.spec_changes, nextState.spec_changes); - const expectedHash = nativeAdvanceEvidenceHash({ summary: event.summary }); - const validSourcePhase = previousState.phase === "archive" && previousState.verification_result === "pass" || previousState.phase === "verify" && previousState.verification_result === "pending"; - if (!validSourcePhase || nextState.phase !== "build" || nextState.verification_result !== "pending" || nextState.verification_report !== null || event.verificationResult !== null || event.artifacts.length !== 0 || event.noCodeReason !== null || envelope.evidenceHash !== expectedHash || !sameIdentity3 || previousRefs === null || nextRefs === null || nextRefs.implementation_scope !== null || nextRefs.verification_evidence !== null || nextRefs.partial_allowance !== null) { - throw new Error("Native transition journal evidence retreat semantics are invalid"); + for (const item of [...started, ...transitioned]) { + const expected = expectedTrajectoryEvent({ + sequence: item.index + 1, + journal, + type: item.event.type + }); + if (!sameValue(trajectoryEventForComparison(item.event), expected)) { + throw new Error( + `Native trajectory event changed for transition ${journal.id}; recovery journal was preserved` + ); } - return; } - if (operation === "spec-rebase") { - const currentHash = specRebaseEvidenceHash( - previousState.name, - event.summary, - nextState.spec_changes + return { + started: started[0]?.event ?? null, + transitioned: transitioned[0]?.event ?? null + }; +} +async function continueNativeTransitionLocked(paths, name, hooks) { + if (await hasPendingNativeSchemaMigration(paths, name)) { + throw new Error(`Native schema migration is incomplete for ${name}; run doctor --repair`); + } + await assertNativeTrajectoryHealthy(paths, name); + const journal = await inspectPendingNativeTransition(paths, name); + if (!journal) return null; + const changeDir = nativeChangeDir(paths, name); + const initialEvents = inspectExistingTransitionEvents( + await readNativeTrajectory(changeDir, journal.nextRun.trajectoryRef), + journal + ); + const [actualRun, actualChange] = await Promise.all([ + readNativeRunState(changeDir), + readNativeChange(paths, name) + ]); + const runSource = assertRunRecoverySource(actualRun, journal); + const changeSource = assertChangeRecoverySource(actualChange, journal); + if ((initialEvents.started || initialEvents.transitioned) && (runSource !== "next" || changeSource !== "next")) { + throw new Error( + `Native trajectory is ahead of transition state for ${journal.change}; recovery journal was preserved` ); - const legacyHash = sha256Text(`spec-rebase:${previousState.name}:${event.summary}`); - if (previousState.phase === "shape" || nextState.phase !== "build" || event.verificationResult !== null || event.artifacts.length !== 0 || event.noCodeReason !== null || nextState.verification_result !== "pending" || nextState.verification_report !== null || !legacyOperation && envelope.evidenceHash !== currentHash || legacyOperation && envelope.evidenceHash !== currentHash && envelope.evidenceHash !== legacyHash || "implementation_scope" in nextState && (nextState.implementation_scope !== null || nextState.verification_evidence !== null || nextState.partial_allowance !== null)) { - throw new Error("Native transition journal spec rebase semantics are invalid"); - } - return; } - if (previousState.phase === "shape") { - if (nextState.phase !== "build" || event.verificationResult !== null || event.artifacts.length !== 0 || event.noCodeReason !== null || previousState.verification_result !== "pending" || nextState.verification_result !== "pending" || previousState.verification_report !== null || nextState.verification_report !== null || previousRefs !== null && nextRefs !== null && (previousRefs.implementation_scope !== null || previousRefs.verification_evidence !== null || previousRefs.partial_allowance !== null || nextRefs.implementation_scope !== null || nextRefs.verification_evidence !== null || nextRefs.partial_allowance !== null)) { - throw new Error("Native transition journal Shape to Build semantics are invalid"); - } - return; + if (runSource === "previous") { + await writeNativeRunState(changeDir, journal.nextRun); + await hooks?.afterRunStateWritten?.(journal); } - if (previousState.phase === "build") { - if (nextState.phase !== "verify" || event.verificationResult !== null || event.artifacts.length === 0 && event.noCodeReason === null || nextState.verification_result !== "pending" || nextState.verification_report !== null || previousRefs !== null && nextRefs !== null && (nextRefs.implementation_scope === null || nextRefs.verification_evidence !== null || nextRefs.implementation_scope !== previousRefs.implementation_scope && nextRefs.partial_allowance !== null && nextRefs.partial_allowance === previousRefs.partial_allowance)) { - throw new Error("Native transition journal Build to Verify semantics are invalid"); - } - return; + if (!sameValue(await readNativeRunState(changeDir), journal.nextRun)) { + throw new Error( + `Native transition Run content changed while continuing ${journal.change}; recovery journal was preserved` + ); } - if (previousState.phase === "verify") { - const expectedNext = event.verificationResult === "pass" ? "archive" : "build"; - if (event.verificationResult !== "pass" && event.verificationResult !== "fail" || nextState.phase !== expectedNext || event.artifacts.length !== 0 || event.noCodeReason !== null || nextState.verification_result !== event.verificationResult || nextState.verification_report === null || previousRefs !== null && nextRefs !== null && (previousRefs.implementation_scope === null || nextRefs.implementation_scope !== previousRefs.implementation_scope || nextRefs.partial_allowance !== previousRefs.partial_allowance || previousRefs.verification_evidence !== null || nextRefs.verification_evidence === null)) { - throw new Error("Native transition journal Verify outcome semantics are invalid"); + if (changeSource === "previous") { + const persisted = await compareAndSwapNativeChangeLocked( + paths, + journal.nextState, + journal.previousState.revision + ); + if (!sameValue(persisted, journal.nextState)) { + throw new Error( + `Native transition change write diverged for ${journal.change}; recovery journal was preserved` + ); } - return; + await hooks?.afterChangeStateWritten?.(journal); } - throw new Error("Native transition journal cannot advance from Archive"); -} -function parseNativeTransitionJournalValue(value, expectedName) { - const journal = journalRecord(value); - rejectUnknownJournalFields(journal, CURRENT_JOURNAL_KEYS); - if (journal.schema !== NATIVE_TRANSITION_SCHEMA) { - throw new Error(`Expected Native transition schema ${NATIVE_TRANSITION_SCHEMA}`); + if (!sameValue(await readNativeChange(paths, name), journal.nextState)) { + throw new Error( + `Native transition change content changed while continuing ${journal.change}; recovery journal was preserved` + ); } - const minimumRuntimeVersion = positiveInteger5( - journal.minimum_runtime_version, - "Native transition minimum_runtime_version" - ); - if (minimumRuntimeVersion > NATIVE_RUNTIME_PROTOCOL_VERSION) { + const activeJournal = await inspectPendingNativeTransition(paths, name); + if (!activeJournal || !sameValue(activeJournal, journal)) { throw new Error( - `Native transition requires runtime protocol ${minimumRuntimeVersion}; current protocol is ${NATIVE_RUNTIME_PROTOCOL_VERSION}` + `Native transition journal changed while continuing ${journal.change}; it was preserved` ); } - if (minimumRuntimeVersion !== NATIVE_RUNTIME_PROTOCOL_VERSION) { + const existingEvents = inspectExistingTransitionEvents( + await readNativeTrajectory(changeDir, journal.nextRun.trajectoryRef), + journal + ); + if (journal.previousRun === null) { + if (!existingEvents.started) { + await appendNativeTrajectoryEvent({ + changeDir, + run: journal.nextRun, + type: "run_started", + data: { + runtime: "comet-native", + phase: journal.previousState.phase, + transitionId: journal.id + }, + now: new Date(journal.createdAt) + }); + } + } + let event = existingEvents.transitioned; + if (!event) { + event = await appendNativeTrajectoryEvent({ + changeDir, + run: journal.nextRun, + type: "state_transitioned", + data: { ...journal.eventData, transitionId: journal.id }, + now: new Date(journal.createdAt) + }); + } + await writeNativeCheckpoint2({ + changeDir, + run: journal.nextRun, + trajectoryOffset: event.sequence, + evidenceHash: journal.evidenceHash, + now: new Date(journal.createdAt) + }); + const [finalRun, finalChange] = await Promise.all([ + readNativeRunState(changeDir), + readNativeChange(paths, name) + ]); + assertRunRecoverySource(finalRun, { ...journal, previousRun: journal.nextRun }); + assertChangeRecoverySource(finalChange, { + ...journal, + previousState: journal.nextState + }); + const finalJournal = await inspectPendingNativeTransition(paths, name); + if (!finalJournal || !sameValue(finalJournal, journal)) { throw new Error( - `Native transition ${NATIVE_TRANSITION_SCHEMA} minimum_runtime_version must be ${NATIVE_RUNTIME_PROTOCOL_VERSION}` + `Native transition journal changed while continuing ${journal.change}; it was not removed` ); } - const revision = positiveInteger5(journal.revision, "Native transition revision"); - if (revision !== 1) throw new Error("Native transition journal revision must be 1"); - if (journal.operation !== "advance" && journal.operation !== "spec-rebase" && journal.operation !== "evidence-retreat") { - throw new Error("Native transition journal operation is invalid"); + await fs16.rm(nativeTransitionJournalFile(paths, name), { force: true }); + return journal.nextState; +} +async function continueNativeTransition(paths, name, hooks) { + return withNativeMutationLock( + paths, + `continue transition ${name}`, + () => withNativeTransitionLock( + paths, + name, + `continue transition ${name}`, + () => continueNativeTransitionLocked(paths, name, hooks) + ) + ); +} + +// domains/comet-native/native-verification-runtime.ts +import path37 from "node:path"; + +// domains/comet-native/native-acceptance.ts +import path27 from "node:path"; +var ACCEPTANCE_HASH_TAG = "comet.native.acceptance.v1"; +var ACCEPTANCE_ID_PATTERN2 = /^acceptance-[a-f0-9]{64}$/u; +var EVIDENCE_ENTRY_KEYS = /* @__PURE__ */ new Set([ + "acceptance_id", + "status", + "evidence_refs", + "skipped_reason", + "waiver_ref" +]); +var TYPED_RECEIPT_REF_PATTERN = /^runtime\/evidence\/receipts\/[a-f0-9]{64}\.json$/u; +var WAIVER_RECEIPT_REF_PATTERN = /^runtime\/evidence\/waivers\/[a-f0-9]{64}\.json$/u; +var ACCEPTANCE_HASH_PATTERN = /^[a-f0-9]{64}$/u; +var ACCEPTANCE_CURSOR_PATTERN = /^native-acceptance-v1\.([a-f0-9]{64})\.([0-9a-z]+)\.([a-f0-9]{64})$/u; +function compareText7(left, right) { + if (left < right) return -1; + if (left > right) return 1; + return 0; +} +var NATIVE_ACCEPTANCE_PAGE_LIMITS = Object.freeze({ + maxItems: 16, + maxTextBytes: 512, + maxContextItems: 4, + maxContextItemBytes: 256, + maxFailedCheckIds: 16, + maxSerializedBytes: 32 * 1024 +}); +var NATIVE_ACCEPTANCE_LIMITS = Object.freeze({ + maxCriteria: 1024 +}); +var NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER = ""; +var NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER = ""; +function truncateUtf8(value, maxBytes) { + if (Buffer.byteLength(value, "utf8") <= maxBytes) return { value, truncated: false }; + let result2 = ""; + let bytes = 0; + for (const character of value) { + const characterBytes = Buffer.byteLength(character, "utf8"); + if (bytes + characterBytes > maxBytes) break; + result2 += character; + bytes += characterBytes; } - const envelope = validateJournalEnvelope(journal, expectedName); - const previousState = parseNativeChangeValue(journal.previousState); - const nextState = parseNativeChangeValue(journal.nextState); - if (previousState.name !== expectedName || nextState.name !== expectedName) { - throw new Error("Native transition journal state mismatch"); + return { value: result2, truncated: true }; +} +function acceptanceCursor(acceptanceHash, offset) { + const encodedOffset = offset.toString(36); + const cursorHash = canonicalHash("comet.native.acceptance-cursor.v1", { + acceptanceHash, + offset + }); + return `native-acceptance-v1.${acceptanceHash}.${encodedOffset}.${cursorHash}`; +} +function acceptanceOffset(options) { + if (!ACCEPTANCE_HASH_PATTERN.test(options.acceptanceHash)) { + throw new Error("Native acceptance page hash is invalid"); } - validateTransitionStateSemantics(previousState, nextState, envelope, journal.operation); - validateTransitionRunSemantics(previousState, nextState, envelope); - if (nextState.revision !== previousState.revision + 1) { - throw new Error("Native transition journal state revision must advance exactly once"); + if (options.cursor === void 0 || options.cursor === null) return 0; + const match = ACCEPTANCE_CURSOR_PATTERN.exec(options.cursor); + if (!match) throw new Error("Native acceptance cursor is invalid"); + if (match[1] !== options.acceptanceHash) { + throw new Error("Native acceptance cursor is stale"); + } + const offset = Number.parseInt(match[2], 36); + if (!Number.isSafeInteger(offset) || offset <= 0 || offset >= options.total || offset.toString(36) !== match[2]) { + throw new Error("Native acceptance cursor offset is invalid"); + } + if (match[3] !== canonicalHash("comet.native.acceptance-cursor.v1", { + acceptanceHash: options.acceptanceHash, + offset + })) { + throw new Error("Native acceptance cursor integrity check failed"); } + return offset; +} +function acceptanceProjection(criterion2, verificationStatus) { + const text3 = truncateUtf8(criterion2.text, NATIVE_ACCEPTANCE_PAGE_LIMITS.maxTextBytes); + const projectedContext = criterion2.context.slice(0, NATIVE_ACCEPTANCE_PAGE_LIMITS.maxContextItems).map((entry2) => truncateUtf8(entry2, NATIVE_ACCEPTANCE_PAGE_LIMITS.maxContextItemBytes)); return { - schema: NATIVE_TRANSITION_SCHEMA, - minimum_runtime_version: NATIVE_RUNTIME_PROTOCOL_VERSION, - revision, - operation: journal.operation, - id: envelope.id, - change: expectedName, - evidenceHash: envelope.evidenceHash, - createdAt: envelope.createdAt, - previousState, - nextState, - previousRun: envelope.previousRun, - nextRun: envelope.nextRun, - eventData: envelope.eventData + id: criterion2.id, + kind: criterion2.kind, + source: criterion2.source, + context: projectedContext.map((entry2) => entry2.value), + text: text3.value, + contextTruncated: criterion2.context.length > projectedContext.length || projectedContext.some((entry2) => entry2.truncated), + textTruncated: text3.truncated, + verificationStatus + }; +} +function projectNativeAcceptancePage(options) { + const offset = acceptanceOffset({ + acceptanceHash: options.acceptanceHash, + total: options.criteria.length, + cursor: options.cursor + }); + const items = []; + const failedCheckIds = [...new Set(options.failedCheckIds ?? [])].sort(compareText7); + const projectedFailedCheckIds = failedCheckIds.slice( + 0, + NATIVE_ACCEPTANCE_PAGE_LIMITS.maxFailedCheckIds + ); + const remaining = options.criteria.slice(offset, offset + NATIVE_ACCEPTANCE_PAGE_LIMITS.maxItems); + for (const criterion2 of remaining) { + const candidate = [ + ...items, + acceptanceProjection( + criterion2, + options.verificationStatuses?.get(criterion2.id) ?? "unverified" + ) + ]; + const nextOffset2 = offset + candidate.length; + const trial = { + schema: "comet.native.acceptance-page.v1", + acceptanceHash: options.acceptanceHash, + total: options.criteria.length, + offset, + items: candidate, + failedAcceptanceIds: candidate.filter((item) => item.verificationStatus === "failed").map((item) => item.id), + missingAcceptanceIds: candidate.filter((item) => item.verificationStatus === "missing").map((item) => item.id), + failedCheckIds: projectedFailedCheckIds, + failedCheckIdsTruncated: failedCheckIds.length > projectedFailedCheckIds.length, + nextCursor: nextOffset2 < options.criteria.length ? acceptanceCursor(options.acceptanceHash, nextOffset2) : null, + limits: { ...NATIVE_ACCEPTANCE_PAGE_LIMITS } + }; + if (Buffer.byteLength(JSON.stringify(trial), "utf8") > NATIVE_ACCEPTANCE_PAGE_LIMITS.maxSerializedBytes) { + if (items.length === 0) { + throw new Error("Native acceptance criterion exceeds the page serialization budget"); + } + break; + } + items.push(candidate.at(-1)); + } + if (options.criteria.length > 0 && items.length === 0) { + throw new Error("Native acceptance page could not project its next criterion"); + } + const nextOffset = offset + items.length; + const page = { + schema: "comet.native.acceptance-page.v1", + acceptanceHash: options.acceptanceHash, + total: options.criteria.length, + offset, + items, + failedAcceptanceIds: items.filter((item) => item.verificationStatus === "failed").map((item) => item.id), + missingAcceptanceIds: items.filter((item) => item.verificationStatus === "missing").map((item) => item.id), + failedCheckIds: projectedFailedCheckIds, + failedCheckIdsTruncated: failedCheckIds.length > projectedFailedCheckIds.length, + nextCursor: nextOffset < options.criteria.length ? acceptanceCursor(options.acceptanceHash, nextOffset) : null, + limits: { ...NATIVE_ACCEPTANCE_PAGE_LIMITS } }; -} -function parseLegacyNativeTransitionJournalValue(value, expectedName) { - const journal = journalRecord(value); - rejectUnknownJournalFields(journal, LEGACY_JOURNAL_KEYS); - if (journal.schema !== NATIVE_LEGACY_TRANSITION_SCHEMA) { - throw new Error(`Expected Native transition schema ${NATIVE_LEGACY_TRANSITION_SCHEMA}`); - } - const envelope = validateJournalEnvelope(journal, expectedName, true); - const previousState = parseLegacyNativeChangeValue(journal.previousState); - const nextState = parseLegacyNativeChangeValue(journal.nextState); - if (previousState.name !== expectedName || nextState.name !== expectedName) { - throw new Error("Native transition journal state mismatch"); + if (Buffer.byteLength(JSON.stringify(page), "utf8") > NATIVE_ACCEPTANCE_PAGE_LIMITS.maxSerializedBytes) { + throw new Error("Native acceptance page exceeds its serialization budget"); } - validateTransitionStateSemantics( - previousState, - nextState, - envelope, - inferredLegacyTransitionOperation(previousState, nextState, envelope.eventData), - true - ); - validateTransitionRunSemantics(previousState, nextState, envelope, true); + return page; +} +function markdownHeading(line) { + const match = /^ {0,3}(#{1,6})[ \t]+(.+?)[ \t]*$/u.exec(line); + if (!match) return null; return { - schema: NATIVE_LEGACY_TRANSITION_SCHEMA, - id: envelope.id, - change: expectedName, - evidenceHash: envelope.evidenceHash, - createdAt: envelope.createdAt, - previousState, - nextState, - previousRun: envelope.previousRun, - nextRun: envelope.nextRun, - eventData: envelope.eventData + level: match[1].length, + text: match[2].replace(/[ \t]+#+[ \t]*$/u, "").trim() }; } -function parseV2NativeTransitionJournalValue(value, expectedName) { - const journal = journalRecord(value); - rejectUnknownJournalFields(journal, V2_JOURNAL_KEYS); - if (journal.schema !== NATIVE_V2_TRANSITION_SCHEMA) { - throw new Error(`Expected Native transition schema ${NATIVE_V2_TRANSITION_SCHEMA}`); - } - const minimumRuntimeVersion = positiveInteger5( - journal.minimum_runtime_version, - "Native v2 transition minimum_runtime_version" - ); - if (minimumRuntimeVersion !== 2) { - throw new Error( - `Native transition ${NATIVE_V2_TRANSITION_SCHEMA} minimum_runtime_version must be 2` - ); +function nextFenceState(line, current) { + const match = /^ {0,3}(`{3,}|~{3,})(.*)$/u.exec(line); + if (!match) return current; + const marker = match[1][0]; + if (current === null) return { marker, length: match[1].length }; + if (marker === current.marker && match[1].length >= current.length && match[2].trim().length === 0) { + return null; } - const revision = positiveInteger5(journal.revision, "Native v2 transition revision"); - if (revision !== 1) throw new Error("Native v2 transition journal revision must be 1"); - const envelope = validateJournalEnvelope(journal, expectedName, true); - const previousState = parseV2NativeChangeValue(journal.previousState); - const nextState = parseV2NativeChangeValue(journal.nextState); - if (previousState.name !== expectedName || nextState.name !== expectedName) { - throw new Error("Native v2 transition journal state mismatch"); + return current; +} +function closesHtmlComment(line) { + return line.includes("-->") || line.includes("--!>"); +} +function* iterateScannedMarkdown(markdown) { + let fence = null; + let htmlComment = false; + let opaqueHtmlTag = null; + const opaqueHtmlTags = /* @__PURE__ */ new Set(["code", "pre", "script", "style", "svg", "template"]); + const normalized = markdown.replace(/\r\n?/gu, "\n"); + let start = 0; + let index = 0; + while (start <= normalized.length) { + const end = normalized.indexOf("\n", start); + const line = end === -1 ? normalized.slice(start) : normalized.slice(start, end); + const body = fence === null && !htmlComment && opaqueHtmlTag === null; + yield { line, body, index }; + index += 1; + if (fence !== null) { + fence = nextFenceState(line, fence); + } else if (htmlComment) { + if (closesHtmlComment(line)) htmlComment = false; + } else if (opaqueHtmlTag !== null) { + if (new RegExp(``, "iu").test(line)) opaqueHtmlTag = null; + } else { + const nextFence = nextFenceState(line, null); + if (nextFence !== null) { + fence = nextFence; + } else { + const trimmed = line.trimStart(); + if (trimmed.startsWith("" || trimmed === "--!>" || /^<\/?[A-Za-z][^>]*>$/u.test(trimmed) || /^<[A-Za-z][^>]*>.*<\/[A-Za-z][^>]*>$/u.test(trimmed) || /^(?:[-*_]\s*){3,}$/u.test(trimmed) || /^\|?(?:\s*:?-{3,}:?\s*\|)+\s*$/u.test(trimmed)) { + flush(); + continue; + } + const item = /^\s*(?:[-*+]|\d+[.)])[ \t]+(.+)$/u.exec(line); + if (item) { + flush(); + active = { + parts: [item[1]], + context: ancestry.map((entry2) => entry2.text) + }; + continue; + } + if (/^\|.*\|$/u.test(trimmed)) { + flush(); + active = { + parts: [trimmed], + context: ancestry.map((entry2) => entry2.text) + }; + flush(); + continue; + } + if (active === null) { + active = { + parts: [trimmed], + context: ancestry.map((entry2) => entry2.text) + }; + } else { + active.parts.push(trimmed); } } - return { - started: started[0]?.event ?? null, - transitioned: transitioned[0]?.event ?? null - }; + flush(); + return uniqueCriteria(criteria, "Specification mandatory requirements"); } -async function continueNativeTransitionLocked(paths, name, hooks) { - if (await hasPendingNativeSchemaMigration(paths, name)) { - throw new Error(`Native schema migration is incomplete for ${name}; run doctor --repair`); +function normalizeEvidenceRef(value, acceptanceId2) { + const normalized = value.trim().replaceAll("\\", "/"); + if (normalized.length === 0 || hasControlCharacter2(normalized) || path27.posix.isAbsolute(normalized) || /^(?:[A-Za-z]:|~|[A-Za-z][A-Za-z0-9+.-]*:)/u.test(normalized) || normalized.split("/").includes("..")) { + throw new Error(`Acceptance evidence ${acceptanceId2} has an unsafe evidence ref`); } - await assertNativeTrajectoryHealthy(paths, name); - const journal = await inspectPendingNativeTransition(paths, name); - if (!journal) return null; - const changeDir = nativeChangeDir(paths, name); - const initialEvents = inspectExistingTransitionEvents( - await readNativeTrajectory(changeDir, journal.nextRun.trajectoryRef), - journal - ); - const [actualRun, actualChange] = await Promise.all([ - readNativeRunState(changeDir), - readNativeChange(paths, name) - ]); - const runSource = assertRunRecoverySource(actualRun, journal); - const changeSource = assertChangeRecoverySource(actualChange, journal); - if ((initialEvents.started || initialEvents.transitioned) && (runSource !== "next" || changeSource !== "next")) { + const portable = path27.posix.normalize(normalized); + if (portable === "." || portable === ".." || portable.startsWith("../")) { + throw new Error(`Acceptance evidence ${acceptanceId2} has an unsafe evidence ref`); + } + if (portable.split("/").some( + (segment) => segment.toLowerCase() === ".git" || segment.toLowerCase().startsWith(".env") + )) { + throw new Error(`Acceptance evidence ${acceptanceId2} references sensitive content`); + } + if (!TYPED_RECEIPT_REF_PATTERN.test(portable)) { throw new Error( - `Native trajectory is ahead of transition state for ${journal.change}; recovery journal was preserved` + `Acceptance evidence ${acceptanceId2} must reference a content-addressed typed receipt` ); } - if (runSource === "previous") { - await writeNativeRunState(changeDir, journal.nextRun); - await hooks?.afterRunStateWritten?.(journal); + return portable; +} +function evidenceRecord2(value, index) { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`Acceptance evidence entry ${index} must be an object`); } - if (!sameValue(await readNativeRunState(changeDir), journal.nextRun)) { + const record12 = value; + const unknown = Object.keys(record12).filter((key) => !EVIDENCE_ENTRY_KEYS.has(key)); + if (unknown.length > 0) { + if (unknown.includes("waiver")) { + throw new Error( + `Acceptance evidence entry ${index} must reference a content-addressed waiver receipt` + ); + } throw new Error( - `Native transition Run content changed while continuing ${journal.change}; recovery journal was preserved` + `Acceptance evidence entry ${index} has unknown field(s): ${unknown.join(", ")}` ); } - if (changeSource === "previous") { - const persisted = await compareAndSwapNativeChangeLocked( - paths, - journal.nextState, - journal.previousState.revision - ); - if (!sameValue(persisted, journal.nextState)) { + return record12; +} +function validateEvidenceEntries(value) { + if (!Array.isArray(value)) + throw new Error("Native acceptance evidence block must be a JSON array"); + const seenIds = /* @__PURE__ */ new Set(); + return value.map((item, index) => { + const record12 = evidenceRecord2(item, index); + const acceptanceId2 = record12.acceptance_id; + if (typeof acceptanceId2 !== "string" || !ACCEPTANCE_ID_PATTERN2.test(acceptanceId2)) { + throw new Error(`Acceptance evidence entry ${index} has an invalid acceptance_id`); + } + if (seenIds.has(acceptanceId2)) { + throw new Error(`Native acceptance evidence has duplicate acceptance_id: ${acceptanceId2}`); + } + seenIds.add(acceptanceId2); + if (!Array.isArray(record12.evidence_refs)) { + throw new Error(`Acceptance evidence ${acceptanceId2} requires an evidence_refs array`); + } + const evidenceRefs = record12.evidence_refs.map((reference) => { + if (typeof reference !== "string" || reference.trim().length === 0) { + throw new Error(`Acceptance evidence ${acceptanceId2} has a non-empty string requirement`); + } + return normalizeEvidenceRef(reference, acceptanceId2); + }); + if (new Set(evidenceRefs).size !== evidenceRefs.length) { + throw new Error(`Acceptance evidence ${acceptanceId2} has a duplicate evidence ref`); + } + const status = record12.status; + if (status !== "passed" && status !== "failed" && status !== "waived") { + throw new Error(`Acceptance evidence ${acceptanceId2} status is invalid`); + } + let skippedReason; + if (Object.prototype.hasOwnProperty.call(record12, "skipped_reason")) { + if (typeof record12.skipped_reason !== "string" || record12.skipped_reason.trim().length === 0) { + throw new Error( + `Acceptance evidence ${acceptanceId2} skipped_reason must be a non-empty string` + ); + } + skippedReason = record12.skipped_reason.trim(); + } + let waiverRef2; + if (Object.prototype.hasOwnProperty.call(record12, "waiver_ref")) { + if (typeof record12.waiver_ref !== "string" || !WAIVER_RECEIPT_REF_PATTERN.test(record12.waiver_ref)) { + throw new Error( + `Acceptance evidence ${acceptanceId2} waiver_ref must identify a content-addressed waiver receipt` + ); + } + waiverRef2 = record12.waiver_ref; + } + if (status === "passed" && evidenceRefs.length === 0) { + throw new Error(`Acceptance evidence ${acceptanceId2} passed status requires evidence_refs`); + } + if (status === "failed" && (evidenceRefs.length > 0 || skippedReason === void 0 || waiverRef2 !== void 0)) { throw new Error( - `Native transition change write diverged for ${journal.change}; recovery journal was preserved` + `Acceptance evidence ${acceptanceId2} failed status requires a skipped_reason and no evidence` ); } - await hooks?.afterChangeStateWritten?.(journal); - } - if (!sameValue(await readNativeChange(paths, name), journal.nextState)) { - throw new Error( - `Native transition change content changed while continuing ${journal.change}; recovery journal was preserved` - ); - } - const activeJournal = await inspectPendingNativeTransition(paths, name); - if (!activeJournal || !sameValue(activeJournal, journal)) { - throw new Error( - `Native transition journal changed while continuing ${journal.change}; it was preserved` - ); + if (status === "waived" && (evidenceRefs.length > 0 || waiverRef2 === void 0 || skippedReason !== void 0)) { + throw new Error( + `Acceptance evidence ${acceptanceId2} waived status requires a waiver receipt and no evidence` + ); + } + if (status === "passed" && (skippedReason !== void 0 || waiverRef2 !== void 0)) { + throw new Error(`Acceptance evidence ${acceptanceId2} passed status has invalid fields`); + } + return { + acceptance_id: acceptanceId2, + status, + evidence_refs: evidenceRefs, + ...skippedReason === void 0 ? {} : { skipped_reason: skippedReason }, + ...waiverRef2 === void 0 ? {} : { waiver_ref: waiverRef2 } + }; + }); +} +function parseNativeVerificationMachineBlock(markdown) { + const lines = scanMarkdown(markdown); + const invalidContextMarker = lines.some( + ({ line, body }) => !body && (line === NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER || line === NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER) + ); + if (invalidContextMarker) { + throw new Error("Native acceptance evidence markers must be in the Markdown body"); } - const existingEvents = inspectExistingTransitionEvents( - await readNativeTrajectory(changeDir, journal.nextRun.trajectoryRef), - journal + const starts = lines.flatMap( + ({ line, body }, index) => body && line === NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER ? [index] : [] ); - if (journal.previousRun === null) { - if (!existingEvents.started) { - await appendNativeTrajectoryEvent({ - changeDir, - run: journal.nextRun, - type: "run_started", - data: { - runtime: "comet-native", - phase: journal.previousState.phase, - transitionId: journal.id - }, - now: new Date(journal.createdAt) - }); - } + const ends = lines.flatMap( + ({ line, body }, index) => body && line === NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER ? [index] : [] + ); + if (starts.length !== 1 || ends.length !== 1) { + throw new Error("Verification must contain exactly one Native acceptance evidence block"); } - let event = existingEvents.transitioned; - if (!event) { - event = await appendNativeTrajectoryEvent({ - changeDir, - run: journal.nextRun, - type: "state_transitioned", - data: { ...journal.eventData, transitionId: journal.id }, - now: new Date(journal.createdAt) - }); + if (starts[0] >= ends[0]) { + throw new Error("Native acceptance evidence markers are out of order"); } - await writeNativeCheckpoint2({ - changeDir, - run: journal.nextRun, - trajectoryOffset: event.sequence, - evidenceHash: journal.evidenceHash, - now: new Date(journal.createdAt) - }); - const [finalRun, finalChange] = await Promise.all([ - readNativeRunState(changeDir), - readNativeChange(paths, name) - ]); - assertRunRecoverySource(finalRun, { ...journal, previousRun: journal.nextRun }); - assertChangeRecoverySource(finalChange, { - ...journal, - previousState: journal.nextState - }); - const finalJournal = await inspectPendingNativeTransition(paths, name); - if (!finalJournal || !sameValue(finalJournal, journal)) { + const payload = lines.slice(starts[0] + 1, ends[0]).map(({ line }) => line).join("\n").trim(); + if (payload.length === 0) throw new Error("Native acceptance evidence block is empty"); + let parsed; + try { + parsed = JSON.parse(payload); + } catch (error) { throw new Error( - `Native transition journal changed while continuing ${journal.change}; it was not removed` + `Native acceptance evidence block is invalid JSON: ${error.message}`, + { cause: error } ); } - await fs18.rm(nativeTransitionJournalFile(paths, name), { force: true }); - return journal.nextState; + const validated = validateEvidenceEntries(parsed); + const canonicalPayload = canonicalEvidencePayload(validated); + if (payload !== canonicalPayload) { + throw new Error("Native acceptance evidence block must use canonical serialization"); + } + return validated; } -async function continueNativeTransition(paths, name, hooks) { - return withNativeMutationLock( - paths, - `continue transition ${name}`, - () => withNativeTransitionLock( - paths, - name, - `continue transition ${name}`, - () => continueNativeTransitionLocked(paths, name, hooks) - ) - ); +function canonicalEvidencePayload(entries) { + const validated = validateEvidenceEntries([...entries]).map((entry2) => ({ ...entry2, evidence_refs: [...entry2.evidence_refs].sort() })).sort((left, right) => left.acceptance_id.localeCompare(right.acceptance_id)); + return JSON.stringify(validated, null, 2); +} +function serializeNativeVerificationMachineBlock(entries) { + return [ + NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER, + canonicalEvidencePayload(entries), + NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER + ].join("\n"); } -// domains/comet-native/native-verification-runtime.ts -import path37 from "node:path"; - -// domains/comet-native/native-acceptance.ts +// domains/comet-native/native-review-trust.ts import path30 from "node:path"; -var ACCEPTANCE_HASH_TAG = "comet.native.acceptance.v1"; -var ACCEPTANCE_ID_PATTERN2 = /^acceptance-[a-f0-9]{64}$/u; -var EVIDENCE_ENTRY_KEYS = /* @__PURE__ */ new Set([ - "acceptance_id", - "status", - "evidence_refs", - "skipped_reason", - "waiver_ref" -]); -var TYPED_RECEIPT_REF_PATTERN = /^runtime\/evidence\/receipts\/[a-f0-9]{64}\.json$/u; -var WAIVER_RECEIPT_REF_PATTERN = /^runtime\/evidence\/waivers\/[a-f0-9]{64}\.json$/u; -var ACCEPTANCE_HASH_PATTERN = /^[a-f0-9]{64}$/u; -var ACCEPTANCE_CURSOR_PATTERN = /^native-acceptance-v1\.([a-f0-9]{64})\.([0-9a-z]+)\.([a-f0-9]{64})$/u; -function compareText7(left, right) { - if (left < right) return -1; - if (left > right) return 1; - return 0; -} -var NATIVE_ACCEPTANCE_PAGE_LIMITS = Object.freeze({ - maxItems: 16, - maxTextBytes: 512, - maxContextItems: 4, - maxContextItemBytes: 256, - maxFailedCheckIds: 16, - maxSerializedBytes: 32 * 1024 -}); -var NATIVE_ACCEPTANCE_LIMITS = Object.freeze({ - maxCriteria: 1024 -}); -var NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER = ""; -var NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER = ""; -function truncateUtf8(value, maxBytes) { - if (Buffer.byteLength(value, "utf8") <= maxBytes) return { value, truncated: false }; - let result2 = ""; - let bytes = 0; - for (const character of value) { - const characterBytes = Buffer.byteLength(character, "utf8"); - if (bytes + characterBytes > maxBytes) break; - result2 += character; - bytes += characterBytes; - } - return { value: result2, truncated: true }; -} -function acceptanceCursor(acceptanceHash, offset) { - const encodedOffset = offset.toString(36); - const cursorHash = canonicalHash("comet.native.acceptance-cursor.v1", { - acceptanceHash, - offset - }); - return `native-acceptance-v1.${acceptanceHash}.${encodedOffset}.${cursorHash}`; -} -function acceptanceOffset(options) { - if (!ACCEPTANCE_HASH_PATTERN.test(options.acceptanceHash)) { - throw new Error("Native acceptance page hash is invalid"); + +// domains/comet-native/native-controller-trust.ts +import { promises as fs18 } from "node:fs"; +import os3 from "node:os"; +import path29 from "node:path"; + +// platform/fs/trusted-readonly-file.ts +import { constants as fsConstants4, promises as fs17 } from "node:fs"; +import path28 from "node:path"; +function trustedReadonlyPosixFactsIssue(facts) { + if (facts.fileUid === facts.currentUid) { + return "Trusted file must be owned by a different host identity"; } - if (options.cursor === void 0 || options.cursor === null) return 0; - const match = ACCEPTANCE_CURSOR_PATTERN.exec(options.cursor); - if (!match) throw new Error("Native acceptance cursor is invalid"); - if (match[1] !== options.acceptanceHash) { - throw new Error("Native acceptance cursor is stale"); + if ((facts.fileMode & 18) !== 0 || facts.fileWritable) { + return "Trusted file is writable by the current process"; } - const offset = Number.parseInt(match[2], 36); - if (!Number.isSafeInteger(offset) || offset <= 0 || offset >= options.total || offset.toString(36) !== match[2]) { - throw new Error("Native acceptance cursor offset is invalid"); + if (facts.parents.some( + (parent) => parent.uid === facts.currentUid || (parent.mode & 18) !== 0 || parent.writable + )) { + return "Trusted file parent chain is writable by the current process"; } - if (match[3] !== canonicalHash("comet.native.acceptance-cursor.v1", { - acceptanceHash: options.acceptanceHash, - offset - })) { - throw new Error("Native acceptance cursor integrity check failed"); + return null; +} +var testHostIsolatedFiles = /* @__PURE__ */ new Set(); +function sameIdentity(left, right) { + return left.realPath === right.realPath && left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs; +} +async function currentProcessCanWrite(file) { + try { + await fs17.access(file, fsConstants4.W_OK); + return true; + } catch (error) { + if (error.code === "EACCES" || error.code === "EPERM") { + return false; + } + throw error; } - return offset; } -function acceptanceProjection(criterion2, verificationStatus) { - const text3 = truncateUtf8(criterion2.text, NATIVE_ACCEPTANCE_PAGE_LIMITS.maxTextBytes); - const projectedContext = criterion2.context.slice(0, NATIVE_ACCEPTANCE_PAGE_LIMITS.maxContextItems).map((entry2) => truncateUtf8(entry2, NATIVE_ACCEPTANCE_PAGE_LIMITS.maxContextItemBytes)); +async function inspectIdentity(file) { + const [stat, realPath] = await Promise.all([fs17.lstat(file), fs17.realpath(file)]); + if (!stat.isFile() || stat.isSymbolicLink()) { + throw new Error("Trusted file capability requires a regular non-symlink file"); + } return { - id: criterion2.id, - kind: criterion2.kind, - source: criterion2.source, - context: projectedContext.map((entry2) => entry2.value), - text: text3.value, - contextTruncated: criterion2.context.length > projectedContext.length || projectedContext.some((entry2) => entry2.truncated), - textTruncated: text3.truncated, - verificationStatus + realPath, + dev: stat.dev, + ino: stat.ino, + size: stat.size, + mtimeMs: stat.mtimeMs }; } -function projectNativeAcceptancePage(options) { - const offset = acceptanceOffset({ - acceptanceHash: options.acceptanceHash, - total: options.criteria.length, - cursor: options.cursor - }); - const items = []; - const failedCheckIds = [...new Set(options.failedCheckIds ?? [])].sort(compareText7); - const projectedFailedCheckIds = failedCheckIds.slice( - 0, - NATIVE_ACCEPTANCE_PAGE_LIMITS.maxFailedCheckIds - ); - const remaining = options.criteria.slice(offset, offset + NATIVE_ACCEPTANCE_PAGE_LIMITS.maxItems); - for (const criterion2 of remaining) { - const candidate = [ - ...items, - acceptanceProjection( - criterion2, - options.verificationStatuses?.get(criterion2.id) ?? "unverified" - ) - ]; - const nextOffset2 = offset + candidate.length; - const trial = { - schema: "comet.native.acceptance-page.v1", - acceptanceHash: options.acceptanceHash, - total: options.criteria.length, - offset, - items: candidate, - failedAcceptanceIds: candidate.filter((item) => item.verificationStatus === "failed").map((item) => item.id), - missingAcceptanceIds: candidate.filter((item) => item.verificationStatus === "missing").map((item) => item.id), - failedCheckIds: projectedFailedCheckIds, - failedCheckIdsTruncated: failedCheckIds.length > projectedFailedCheckIds.length, - nextCursor: nextOffset2 < options.criteria.length ? acceptanceCursor(options.acceptanceHash, nextOffset2) : null, - limits: { ...NATIVE_ACCEPTANCE_PAGE_LIMITS } - }; - if (Buffer.byteLength(JSON.stringify(trial), "utf8") > NATIVE_ACCEPTANCE_PAGE_LIMITS.maxSerializedBytes) { - if (items.length === 0) { - throw new Error("Native acceptance criterion exceeds the page serialization budget"); - } - break; - } - items.push(candidate.at(-1)); +async function assertTrustedReadonlyFile(options) { + const identity = await inspectIdentity(options.file); + if (options.previous && !sameIdentity(options.previous, identity)) { + throw new Error("Trusted file identity changed while reading"); } - if (options.criteria.length > 0 && items.length === 0) { - throw new Error("Native acceptance page could not project its next criterion"); + if (process.env.NODE_ENV === "test" && testHostIsolatedFiles.has(path28.resolve(options.file))) { + return identity; } - const nextOffset = offset + items.length; - const page = { - schema: "comet.native.acceptance-page.v1", - acceptanceHash: options.acceptanceHash, - total: options.criteria.length, - offset, - items, - failedAcceptanceIds: items.filter((item) => item.verificationStatus === "failed").map((item) => item.id), - missingAcceptanceIds: items.filter((item) => item.verificationStatus === "missing").map((item) => item.id), - failedCheckIds: projectedFailedCheckIds, - failedCheckIdsTruncated: failedCheckIds.length > projectedFailedCheckIds.length, - nextCursor: nextOffset < options.criteria.length ? acceptanceCursor(options.acceptanceHash, nextOffset) : null, - limits: { ...NATIVE_ACCEPTANCE_PAGE_LIMITS } - }; - if (Buffer.byteLength(JSON.stringify(page), "utf8") > NATIVE_ACCEPTANCE_PAGE_LIMITS.maxSerializedBytes) { - throw new Error("Native acceptance page exceeds its serialization budget"); + if (process.platform === "win32") { + throw new Error( + "Trusted file isolation cannot be proven from Windows file mode; use a host read-only mount capability" + ); } - return page; -} -function markdownHeading(line) { - const match = /^ {0,3}(#{1,6})[ \t]+(.+?)[ \t]*$/u.exec(line); - if (!match) return null; - return { - level: match[1].length, - text: match[2].replace(/[ \t]+#+[ \t]*$/u, "").trim() - }; -} -function nextFenceState(line, current) { - const match = /^ {0,3}(`{3,}|~{3,})(.*)$/u.exec(line); - if (!match) return current; - const marker = match[1][0]; - if (current === null) return { marker, length: match[1].length }; - if (marker === current.marker && match[1].length >= current.length && match[2].trim().length === 0) { - return null; + const currentUid = process.geteuid?.() ?? process.getuid?.(); + if (currentUid === void 0) { + throw new Error("Trusted file owner isolation is unavailable on this platform"); } - return current; + const fileStat = await fs17.stat(identity.realPath); + const parents = []; + let directory = path28.dirname(identity.realPath); + for (; ; ) { + const stat = await fs17.lstat(directory); + if (!stat.isDirectory() || stat.isSymbolicLink()) { + throw new Error("Trusted file parent chain is not a physical directory chain"); + } + parents.push({ + uid: stat.uid, + mode: stat.mode, + writable: await currentProcessCanWrite(directory) + }); + const parent = path28.dirname(directory); + if (parent === directory) break; + directory = parent; + } + const issue = trustedReadonlyPosixFactsIssue({ + currentUid, + fileUid: fileStat.uid, + fileMode: fileStat.mode, + fileWritable: await currentProcessCanWrite(identity.realPath), + parents + }); + if (issue) throw new Error(issue); + return identity; } -function closesHtmlComment(line) { - return line.includes("-->") || line.includes("--!>"); + +// domains/comet-native/native-controller-trust.ts +var NATIVE_CONTROLLER_TRUST_STORE_SCHEMA = "comet.native.controller-trust-store.v1"; +var NATIVE_CONTROLLER_TRUST_STORE_TEST_ENV = "COMET_NATIVE_CONTROLLER_TRUST_STORE_TEST_PATH"; +var PROJECT_ROOT_HASH_TAG = "comet.native.controller-project-root.v1"; +var MAX_STORE_BYTES = 256 * 1024; +var HASH_PATTERN14 = /^[a-f0-9]{64}$/u; +function record8(value, label) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`${label} must be an object`); + } + return value; } -function* iterateScannedMarkdown(markdown) { - let fence = null; - let htmlComment = false; - let opaqueHtmlTag = null; - const opaqueHtmlTags = /* @__PURE__ */ new Set(["code", "pre", "script", "style", "svg", "template"]); - const normalized = markdown.replace(/\r\n?/gu, "\n"); - let start = 0; - let index = 0; - while (start <= normalized.length) { - const end = normalized.indexOf("\n", start); - const line = end === -1 ? normalized.slice(start) : normalized.slice(start, end); - const body = fence === null && !htmlComment && opaqueHtmlTag === null; - yield { line, body, index }; - index += 1; - if (fence !== null) { - fence = nextFenceState(line, fence); - } else if (htmlComment) { - if (closesHtmlComment(line)) htmlComment = false; - } else if (opaqueHtmlTag !== null) { - if (new RegExp(``, "iu").test(line)) opaqueHtmlTag = null; - } else { - const nextFence = nextFenceState(line, null); - if (nextFence !== null) { - fence = nextFence; - } else { - const trimmed = line.trimStart(); - if (trimmed.startsWith("" || trimmed === "--!>" || /^<\/?[A-Za-z][^>]*>$/u.test(trimmed) || /^<[A-Za-z][^>]*>.*<\/[A-Za-z][^>]*>$/u.test(trimmed) || /^(?:[-*_]\s*){3,}$/u.test(trimmed) || /^\|?(?:\s*:?-{3,}:?\s*\|)+\s*$/u.test(trimmed)) { - flush(); - continue; - } - const item = /^\s*(?:[-*+]|\d+[.)])[ \t]+(.+)$/u.exec(line); - if (item) { - flush(); - active = { - parts: [item[1]], - context: ancestry.map((entry2) => entry2.text) - }; - continue; - } - if (/^\|.*\|$/u.test(trimmed)) { - flush(); - active = { - parts: [trimmed], - context: ancestry.map((entry2) => entry2.text) - }; - flush(); - continue; - } - if (active === null) { - active = { - parts: [trimmed], - context: ancestry.map((entry2) => entry2.text) - }; - } else { - active.parts.push(trimmed); - } + const parsed = value.map(parseNativeReviewIdentity).sort((left, right) => left.keyId.localeCompare(right.keyId, "en")); + if (new Set(parsed.map((identity) => identity.keyId)).size !== parsed.length || JSON.stringify(value) !== JSON.stringify(parsed)) { + throw new Error(`${label} must be sorted and unique`); } - flush(); - return uniqueCriteria(criteria, "Specification mandatory requirements"); + return parsed; +} +function buildNativeReviewTrustPolicy(input) { + const content = { + schema: NATIVE_REVIEW_TRUST_POLICY_SCHEMA, + controllerKeyId: input.controllerIdentity.keyId, + implementationKeyId: input.implementationKeyId, + trustedReviewers: [...input.trustedReviewers].sort( + (left, right) => left.keyId.localeCompare(right.keyId, "en") + ), + trustedWaiverSigners: [...input.trustedWaiverSigners].sort( + (left, right) => left.keyId.localeCompare(right.keyId, "en") + ) + }; + const policyHash = canonicalHash(POLICY_HASH_TAG, content); + return parseNativeReviewTrustPolicy({ + ...content, + policyHash, + controllerSignature: signNativeReviewPayloadHash({ + identity: input.controllerIdentity, + privateKey: input.controllerPrivateKey, + payloadHash: policyHash + }) + }); } -function normalizeEvidenceRef(value, acceptanceId2) { - const normalized = value.trim().replaceAll("\\", "/"); - if (normalized.length === 0 || hasControlCharacter2(normalized) || path30.posix.isAbsolute(normalized) || /^(?:[A-Za-z]:|~|[A-Za-z][A-Za-z0-9+.-]*:)/u.test(normalized) || normalized.split("/").includes("..")) { - throw new Error(`Acceptance evidence ${acceptanceId2} has an unsafe evidence ref`); - } - const portable = path30.posix.normalize(normalized); - if (portable === "." || portable === ".." || portable.startsWith("../")) { - throw new Error(`Acceptance evidence ${acceptanceId2} has an unsafe evidence ref`); - } - if (portable.split("/").some( - (segment) => segment.toLowerCase() === ".git" || segment.toLowerCase().startsWith(".env") - )) { - throw new Error(`Acceptance evidence ${acceptanceId2} references sensitive content`); +function parseNativeReviewTrustPolicy(value) { + const root = record9(value, "Native review trust policy"); + exactKeys7( + root, + [ + "schema", + "controllerKeyId", + "implementationKeyId", + "trustedReviewers", + "trustedWaiverSigners", + "policyHash", + "controllerSignature" + ], + "Native review trust policy" + ); + if (root.schema !== NATIVE_REVIEW_TRUST_POLICY_SCHEMA || typeof root.controllerKeyId !== "string" || !HASH_PATTERN15.test(root.controllerKeyId) || typeof root.implementationKeyId !== "string" || !HASH_PATTERN15.test(root.implementationKeyId) || typeof root.policyHash !== "string" || !HASH_PATTERN15.test(root.policyHash)) { + throw new Error("Native review trust policy identity or hash is invalid"); } - if (!TYPED_RECEIPT_REF_PATTERN.test(portable)) { + const trustedReviewers = identities(root.trustedReviewers, "Native trusted reviewers"); + const trustedWaiverSigners = identities( + root.trustedWaiverSigners, + "Native trusted waiver signers" + ); + if (trustedReviewers.some((identity) => identity.keyId === root.implementationKeyId) || trustedWaiverSigners.some((identity) => identity.keyId === root.implementationKeyId) || (/* @__PURE__ */ new Set([ + root.controllerKeyId, + root.implementationKeyId, + ...trustedReviewers.map((identity) => identity.keyId), + ...trustedWaiverSigners.map((identity) => identity.keyId) + ])).size !== 2 + trustedReviewers.length + trustedWaiverSigners.length) { throw new Error( - `Acceptance evidence ${acceptanceId2} must reference a content-addressed typed receipt` + "Native controller, implementation, reviewer, and waiver signer identities must be globally distinct" ); } - return portable; -} -function evidenceRecord2(value, index) { - if (value === null || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`Acceptance evidence entry ${index} must be an object`); + const content = { + schema: NATIVE_REVIEW_TRUST_POLICY_SCHEMA, + controllerKeyId: root.controllerKeyId, + implementationKeyId: root.implementationKeyId, + trustedReviewers, + trustedWaiverSigners + }; + const policyHash = canonicalHash(POLICY_HASH_TAG, content); + if (policyHash !== root.policyHash) { + throw new Error("Native review trust policy hash mismatch"); } - const record13 = value; - const unknown = Object.keys(record13).filter((key) => !EVIDENCE_ENTRY_KEYS.has(key)); - if (unknown.length > 0) { - if (unknown.includes("waiver")) { - throw new Error( - `Acceptance evidence entry ${index} must reference a content-addressed waiver receipt` - ); - } - throw new Error( - `Acceptance evidence entry ${index} has unknown field(s): ${unknown.join(", ")}` - ); + const controllerSignature = parseNativeReviewSignature(root.controllerSignature); + if (controllerSignature.keyId !== root.controllerKeyId || controllerSignature.payloadHash !== policyHash) { + throw new Error("Native review trust policy controller signature binding is invalid"); } - return record13; + return { ...content, policyHash, controllerSignature }; } -function validateEvidenceEntries(value) { - if (!Array.isArray(value)) - throw new Error("Native acceptance evidence block must be a JSON array"); - const seenIds = /* @__PURE__ */ new Set(); - return value.map((item, index) => { - const record13 = evidenceRecord2(item, index); - const acceptanceId2 = record13.acceptance_id; - if (typeof acceptanceId2 !== "string" || !ACCEPTANCE_ID_PATTERN2.test(acceptanceId2)) { - throw new Error(`Acceptance evidence entry ${index} has an invalid acceptance_id`); - } - if (seenIds.has(acceptanceId2)) { - throw new Error(`Native acceptance evidence has duplicate acceptance_id: ${acceptanceId2}`); - } - seenIds.add(acceptanceId2); - if (!Array.isArray(record13.evidence_refs)) { - throw new Error(`Acceptance evidence ${acceptanceId2} requires an evidence_refs array`); - } - const evidenceRefs = record13.evidence_refs.map((reference) => { - if (typeof reference !== "string" || reference.trim().length === 0) { - throw new Error(`Acceptance evidence ${acceptanceId2} has a non-empty string requirement`); - } - return normalizeEvidenceRef(reference, acceptanceId2); - }); - if (new Set(evidenceRefs).size !== evidenceRefs.length) { - throw new Error(`Acceptance evidence ${acceptanceId2} has a duplicate evidence ref`); - } - const status = record13.status; - if (status !== "passed" && status !== "failed" && status !== "waived") { - throw new Error(`Acceptance evidence ${acceptanceId2} status is invalid`); - } - let skippedReason; - if (Object.prototype.hasOwnProperty.call(record13, "skipped_reason")) { - if (typeof record13.skipped_reason !== "string" || record13.skipped_reason.trim().length === 0) { - throw new Error( - `Acceptance evidence ${acceptanceId2} skipped_reason must be a non-empty string` - ); - } - skippedReason = record13.skipped_reason.trim(); - } - let waiverRef2; - if (Object.prototype.hasOwnProperty.call(record13, "waiver_ref")) { - if (typeof record13.waiver_ref !== "string" || !WAIVER_RECEIPT_REF_PATTERN.test(record13.waiver_ref)) { - throw new Error( - `Acceptance evidence ${acceptanceId2} waiver_ref must identify a content-addressed waiver receipt` - ); - } - waiverRef2 = record13.waiver_ref; - } - if (status === "passed" && evidenceRefs.length === 0) { - throw new Error(`Acceptance evidence ${acceptanceId2} passed status requires evidence_refs`); - } - if (status === "failed" && (evidenceRefs.length > 0 || skippedReason === void 0 || waiverRef2 !== void 0)) { - throw new Error( - `Acceptance evidence ${acceptanceId2} failed status requires a skipped_reason and no evidence` - ); - } - if (status === "waived" && (evidenceRefs.length > 0 || waiverRef2 === void 0 || skippedReason !== void 0)) { - throw new Error( - `Acceptance evidence ${acceptanceId2} waived status requires a waiver receipt and no evidence` - ); - } - if (status === "passed" && (skippedReason !== void 0 || waiverRef2 !== void 0)) { - throw new Error(`Acceptance evidence ${acceptanceId2} passed status has invalid fields`); - } - return { - acceptance_id: acceptanceId2, - status, - evidence_refs: evidenceRefs, - ...skippedReason === void 0 ? {} : { skipped_reason: skippedReason }, - ...waiverRef2 === void 0 ? {} : { waiver_ref: waiverRef2 } - }; +function verifyNativeReviewTrustPolicy(value, controllerIdentity) { + const policy = parseNativeReviewTrustPolicy(value); + if (policy.controllerKeyId !== controllerIdentity.keyId) { + throw new Error("Native review trust policy controller is not host-trusted"); + } + verifyNativeReviewPayloadHash({ + identity: controllerIdentity, + payloadHash: policy.policyHash, + proof: policy.controllerSignature }); + return policy; } -function parseNativeVerificationMachineBlock(markdown) { - const lines = scanMarkdown(markdown); - const invalidContextMarker = lines.some( - ({ line, body }) => !body && (line === NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER || line === NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER) - ); - if (invalidContextMarker) { - throw new Error("Native acceptance evidence markers must be in the Markdown body"); - } - const starts = lines.flatMap( - ({ line, body }, index) => body && line === NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER ? [index] : [] +async function loadNativeReviewTrustPolicy(options) { + const baseline = options.scope.baseline.entries.find( + (entry2) => entry2.path === NATIVE_REVIEW_TRUST_POLICY_REF ); - const ends = lines.flatMap( - ({ line, body }, index) => body && line === NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER ? [index] : [] + const current = options.scope.current.entries.find( + (entry2) => entry2.path === NATIVE_REVIEW_TRUST_POLICY_REF ); - if (starts.length !== 1 || ends.length !== 1) { - throw new Error("Verification must contain exactly one Native acceptance evidence block"); + if (!baseline || !current || baseline.hash !== current.hash || baseline.size !== current.size) { + throw new Error( + "Native review trust policy must exist unchanged from change creation through Build" + ); } - if (starts[0] >= ends[0]) { - throw new Error("Native acceptance evidence markers are out of order"); + const file = await readNativeProtectedTextFile({ + root: options.paths.projectRoot, + file: path30.join(options.paths.projectRoot, ...NATIVE_REVIEW_TRUST_POLICY_REF.split("/")), + maxBytes: MAX_POLICY_BYTES, + label: NATIVE_REVIEW_TRUST_POLICY_REF + }); + if (file.hash !== current.hash || file.size !== current.size) { + throw new Error("Native review trust policy changed after Build"); } - const payload = lines.slice(starts[0] + 1, ends[0]).map(({ line }) => line).join("\n").trim(); - if (payload.length === 0) throw new Error("Native acceptance evidence block is empty"); - let parsed; + let value; try { - parsed = JSON.parse(payload); + value = JSON.parse(file.text); } catch (error) { - throw new Error( - `Native acceptance evidence block is invalid JSON: ${error.message}`, - { cause: error } - ); - } - const validated = validateEvidenceEntries(parsed); - const canonicalPayload = canonicalEvidencePayload(validated); - if (payload !== canonicalPayload) { - throw new Error("Native acceptance evidence block must use canonical serialization"); + throw new Error("Native review trust policy is not valid JSON", { cause: error }); } - return validated; -} -function canonicalEvidencePayload(entries) { - const validated = validateEvidenceEntries([...entries]).map((entry2) => ({ ...entry2, evidence_refs: [...entry2.evidence_refs].sort() })).sort((left, right) => left.acceptance_id.localeCompare(right.acceptance_id)); - return JSON.stringify(validated, null, 2); + const controllerTrust = await readNativeControllerTrustProject(options.paths.projectRoot); + if (!controllerTrust) throw new Error("Native project has no controller-owned trust root"); + return verifyNativeReviewTrustPolicy(value, controllerTrust.controllerIdentity); } -function serializeNativeVerificationMachineBlock(entries) { - return [ - NATIVE_ACCEPTANCE_EVIDENCE_START_MARKER, - canonicalEvidencePayload(entries), - NATIVE_ACCEPTANCE_EVIDENCE_END_MARKER - ].join("\n"); +function trustedNativeIdentity(policy, kind, keyId) { + const identities2 = kind === "reviewer" ? policy.trustedReviewers : policy.trustedWaiverSigners; + const identity = identities2.find((candidate) => candidate.keyId === keyId); + if (!identity) throw new Error(`Native ${kind} identity is not pre-trusted`); + return identity; } // domains/comet-native/native-contract.ts import path31 from "node:path"; var CONTRACT_HASH_TAG = "comet.native.contract.v1"; var ACCEPTANCE_SET_HASH_TAG = "comet.native.acceptance-set.v1"; -var HASH_PATTERN17 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN16 = /^[a-f0-9]{64}$/u; var NATIVE_CONTRACT_LIMITS = { maxAcceptanceCriteria: NATIVE_ACCEPTANCE_LIMITS.maxCriteria }; @@ -21804,7 +21553,7 @@ function normalizeSpec3(input) { throw new Error(`Invalid Native contract operation for ${input.capability}`); } if (input.operation === "remove") { - if (input.source !== null || input.markdown !== null || !input.baseHash?.match(HASH_PATTERN17)) { + if (input.source !== null || input.markdown !== null || !input.baseHash?.match(HASH_PATTERN16)) { throw new Error(`Remove contract ${input.capability} requires only a base hash`); } return { @@ -21824,7 +21573,7 @@ function normalizeSpec3(input) { if (input.operation === "create" && input.baseHash !== null) { throw new Error(`Create contract ${input.capability} requires a null base hash`); } - if (input.operation === "replace" && !input.baseHash?.match(HASH_PATTERN17)) { + if (input.operation === "replace" && !input.baseHash?.match(HASH_PATTERN16)) { throw new Error(`Replace contract ${input.capability} requires a base hash`); } const source = portableRef2(input.source, `Contract source for ${input.capability}`); @@ -21973,7 +21722,7 @@ async function collectNativeContractFiles(options) { // domains/comet-native/native-verification-receipt-runtime.ts import { execFile, spawn as spawn3 } from "node:child_process"; -import { createHash as createHash17 } from "node:crypto"; +import { createHash as createHash16 } from "node:crypto"; import path36 from "node:path"; import { promisify } from "node:util"; @@ -22047,14 +21796,14 @@ var NATIVE_CHECKPOINT_LIMITS = { maxTotalBytes: 64 * 1024 * 1024, maxDocumentBytes: 256 * 1024 }; -var HASH_PATTERN18 = /^[a-f0-9]{64}$/u; -function record11(value, label) { +var HASH_PATTERN17 = /^[a-f0-9]{64}$/u; +function record10(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } return value; } -function exactKeys9(value, keys, label) { +function exactKeys8(value, keys, label) { const expected = new Set(keys); const unknown = Object.keys(value).filter((key) => !expected.has(key)); const missing = keys.filter((key) => !(key in value)); @@ -22097,7 +21846,7 @@ function nativeCheckpointJournalFile(paths, name) { return path33.join(nativeChangeDir(paths, name), "runtime", "checkpoint-journal.json"); } function nativeCheckpointManifestFile(paths, name, hash8) { - if (!HASH_PATTERN18.test(hash8)) throw new Error("Native checkpoint manifest hash is invalid"); + if (!HASH_PATTERN17.test(hash8)) throw new Error("Native checkpoint manifest hash is invalid"); return path33.join( nativeChangeDir(paths, name), "runtime", @@ -22107,7 +21856,7 @@ function nativeCheckpointManifestFile(paths, name, hash8) { ); } function nativeCheckpointManifestRef(hash8) { - if (!HASH_PATTERN18.test(hash8)) throw new Error("Native checkpoint manifest hash is invalid"); + if (!HASH_PATTERN17.test(hash8)) throw new Error("Native checkpoint manifest hash is invalid"); return `runtime/checkpoints/manifests/${hash8}.json`; } async function readBoundedJson(root, file, label) { @@ -22120,8 +21869,8 @@ async function readBoundedJson(root, file, label) { return JSON.parse(snapshot2.bytes.toString("utf8")); } function parseArtifact(value, index) { - const artifact = record11(value, `checkpoint manifest artifact ${index}`); - exactKeys9(artifact, ["path", "hash", "size"], `checkpoint manifest artifact ${index}`); + const artifact = record10(value, `checkpoint manifest artifact ${index}`); + exactKeys8(artifact, ["path", "hash", "size"], `checkpoint manifest artifact ${index}`); const artifactPath = normalizeNativeCheckpointArtifactRef( stringValue(artifact.path, `checkpoint artifact ${index} path`, 4096) ); @@ -22131,7 +21880,7 @@ function parseArtifact(value, index) { `checkpoint artifact ${index} is excluded as sensitive (${sensitiveReason}): ${artifactPath}` ); } - if (typeof artifact.hash !== "string" || !HASH_PATTERN18.test(artifact.hash)) { + if (typeof artifact.hash !== "string" || !HASH_PATTERN17.test(artifact.hash)) { throw new Error(`checkpoint artifact ${index} hash is invalid`); } return { @@ -22141,8 +21890,8 @@ function parseArtifact(value, index) { }; } function parseNativeCheckpointManifestValue(value, expectedName) { - const manifest = record11(value, "Native checkpoint manifest"); - exactKeys9( + const manifest = record10(value, "Native checkpoint manifest"); + exactKeys8( manifest, ["schema", "change", "artifacts", "totalBytes"], "Native checkpoint manifest" @@ -22184,8 +21933,8 @@ function hashNativeCheckpointManifest(manifest) { return sha256Text(JSON.stringify(parseNativeCheckpointManifestValue(manifest, manifest.change))); } function parseNativeProgressCheckpointValue(value, expectedName) { - const checkpoint = record11(value, "Native progress checkpoint"); - exactKeys9( + const checkpoint = record10(value, "Native progress checkpoint"); + exactKeys8( checkpoint, [ "schema", @@ -22224,14 +21973,14 @@ function parseNativeProgressCheckpointValue(value, expectedName) { throw new Error("Native checkpoint stateRevision must increment previousRevision once"); } const manifestHash = stringValue(checkpoint.manifestHash, "Native checkpoint manifestHash", 64); - if (!HASH_PATTERN18.test(manifestHash)) + if (!HASH_PATTERN17.test(manifestHash)) throw new Error("Native checkpoint manifestHash is invalid"); const expectedManifestRef = nativeCheckpointManifestRef(manifestHash); if (checkpoint.manifestRef !== expectedManifestRef) { throw new Error("Native checkpoint manifestRef does not match manifestHash"); } const inputHash = stringValue(checkpoint.inputHash, "Native checkpoint inputHash", 64); - if (!HASH_PATTERN18.test(inputHash)) throw new Error("Native checkpoint inputHash is invalid"); + if (!HASH_PATTERN17.test(inputHash)) throw new Error("Native checkpoint inputHash is invalid"); const createdAt = stringValue(checkpoint.createdAt, "Native checkpoint createdAt", 64); if (Number.isNaN(Date.parse(createdAt))) throw new Error("Native checkpoint createdAt is invalid"); @@ -22264,8 +22013,8 @@ function parseNativeProgressCheckpointValue(value, expectedName) { }; } function parseNativeCheckpointJournalValue(value, expectedName) { - const journal = record11(value, "Native checkpoint journal"); - exactKeys9( + const journal = record10(value, "Native checkpoint journal"); + exactKeys8( journal, [ "schema", @@ -22296,7 +22045,7 @@ function parseNativeCheckpointJournalValue(value, expectedName) { artifacts: manifest.artifacts }) ); - if (!HASH_PATTERN18.test(inputHash) || inputHash !== checkpoint.inputHash || inputHash !== expectedInputHash || journal.id !== checkpoint.id || journal.createdAt !== checkpoint.createdAt) { + if (!HASH_PATTERN17.test(inputHash) || inputHash !== checkpoint.inputHash || inputHash !== expectedInputHash || journal.id !== checkpoint.id || journal.createdAt !== checkpoint.createdAt) { throw new Error("Native checkpoint journal envelope mismatch"); } if (previousState.name !== expectedName || nextState.name !== expectedName || nextState.revision !== previousState.revision + 1 || checkpoint.previousRevision !== previousState.revision || checkpoint.stateRevision !== nextState.revision || checkpoint.phase !== nextState.phase || checkpoint.manifestHash !== hashNativeCheckpointManifest(manifest) || checkpoint.artifactCount !== manifest.artifacts.length) { @@ -22594,7 +22343,7 @@ async function settleNativeChangeJournalsLocked(paths, name) { } // domains/comet-native/native-check-receipt.ts -import { createHash as createHash16 } from "node:crypto"; +import { createHash as createHash15 } from "node:crypto"; import { constants as fsConstants6, promises as fs21 } from "node:fs"; import path35 from "node:path"; import { TextDecoder as TextDecoder5 } from "node:util"; @@ -22602,7 +22351,7 @@ import { TextDecoder as TextDecoder5 } from "node:util"; // domains/comet-native/native-check-receipt-storage.ts import { constants as fsConstants5, promises as fs20 } from "node:fs"; import path34 from "node:path"; -var HASH_PATTERN19 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN18 = /^[a-f0-9]{64}$/u; var RECEIPT_REF_PATTERN2 = /^runtime\/evidence\/check-receipts\/([a-f0-9]{64})\.json$/u; var MAX_NATIVE_CHECK_RECEIPT_BYTES = 512 * 1024; function sameDirectoryIdentity6(identity, stat) { @@ -22662,7 +22411,7 @@ async function verifyDirectoryChain6(chain) { } } function nativeCheckReceiptRef(hash8) { - if (!HASH_PATTERN19.test(hash8)) throw new Error("Native check receipt hash is invalid"); + if (!HASH_PATTERN18.test(hash8)) throw new Error("Native check receipt hash is invalid"); return `runtime/evidence/check-receipts/${hash8}.json`; } function receiptHashFromRef(ref) { @@ -22989,7 +22738,7 @@ async function readScopedFile(options) { throw new ScopedFileError("unsafe-file", "Scoped file changed while opening"); } const chunks = []; - const digest = createHash16("sha256"); + const digest = createHash15("sha256"); const buffer = Buffer.allocUnsafe(64 * 1024); let total = 0; while (true) { @@ -23986,7 +23735,7 @@ async function issueNativeAutomatedCheckReceiptLocked(options) { const output = []; let outputBytes = 0; let totalOutputBytes = 0; - const outputHasher = createHash17("sha256"); + const outputHasher = createHash16("sha256"); let timedOut = false; const child = spawn3(options.command, [...options.args], { cwd: options.paths.projectRoot, @@ -24583,8 +24332,8 @@ async function inspectNativeVerificationEvidence(options) { contractHash: facts.contractHash, implementationScope: facts.bundle }); - const signedVerification = options.state.verification_protocol === "signed-v2"; - if (signedVerification && options.result === "pass" && receiptGraph.independentReviewReceiptRef === null) { + const independentReviewRequired = !facts.bundle.scope.complete || isNativeHighRiskScope(facts.bundle.scope.changes); + if (independentReviewRequired && options.result === "pass" && receiptGraph.independentReviewReceiptRef === null) { throw new Error( "Native passing verification requires a signed acceptance-applicability review receipt" ); @@ -24713,7 +24462,7 @@ async function inspectNativeVerificationFreshness(options) { } catch { findingCodes.push("verification-receipt-invalid"); } - if (options.state.verification_protocol === "signed-v2" && envelope.result === "pass" && envelope.independentReviewReceiptRef === null) { + if ((!facts.bundle.scope.complete || isNativeHighRiskScope(facts.bundle.scope.changes)) && envelope.result === "pass" && envelope.independentReviewReceiptRef === null) { findingCodes.push("verification-independent-review-missing"); } const uniqueCodes = [...new Set(findingCodes)].sort(); @@ -25100,12 +24849,12 @@ function parseCasRecord(value, operation, role, expectedHash) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`Archive CAS ${operation.id} ${role} record must be an object`); } - const record13 = value; - const keys = Object.keys(record13).sort(); + const record12 = value; + const keys = Object.keys(record12).sort(); if (keys.join(",") !== "hash,identity,operationId,role,schema") { throw new Error(`Archive CAS ${operation.id} ${role} record has an invalid shape`); } - const identity = record13.identity; + const identity = record12.identity; if (!identity || typeof identity !== "object" || Array.isArray(identity)) { throw new Error(`Archive CAS ${operation.id} ${role} identity must be an object`); } @@ -25118,7 +24867,7 @@ function parseCasRecord(value, operation, role, expectedHash) { throw new Error(`Archive CAS ${operation.id} ${role} identity ${field2} is invalid`); } } - if (record13.schema !== NATIVE_ARCHIVE_CAS_SCHEMA || record13.operationId !== operation.id || record13.role !== role || record13.hash !== expectedHash) { + if (record12.schema !== NATIVE_ARCHIVE_CAS_SCHEMA || record12.operationId !== operation.id || record12.role !== role || record12.hash !== expectedHash) { throw new Error(`Archive CAS ${operation.id} ${role} record is not transaction-bound`); } return { @@ -25180,7 +24929,7 @@ async function persistCasRecord(options) { directory: path40.dirname(options.file), label: `Archive CAS ${options.operation.id} records` }); - const record13 = { + const record12 = { schema: NATIVE_ARCHIVE_CAS_SCHEMA, operationId: options.operation.id, role: options.role, @@ -25188,7 +24937,7 @@ async function persistCasRecord(options) { identity: options.identity }; try { - await atomicWriteJson(options.file, record13, { + await atomicWriteJson(options.file, record12, { containedRoot: options.paths.nativeRoot, exclusive: true }); @@ -25219,7 +24968,7 @@ async function bindCurrentTarget(options) { expectedHash: options.expectedHash, label: options.label }); - const record13 = await persistCasRecord({ + const record12 = await persistCasRecord({ paths: options.paths, file: options.recordFile, operation: options.operation, @@ -25231,10 +24980,10 @@ async function bindCurrentTarget(options) { paths: options.paths, file: options.file, operation: options.operation, - record: record13, + record: record12, label: options.label }); - return record13; + return record12; } async function restoreUnexpectedQuarantine(options) { if (!await pathExists(options.quarantine) || await pathExists(options.target)) return; @@ -25378,7 +25127,7 @@ async function ensureOriginalTargetQuarantined(options) { options.operation, options.target ); - let record13 = await readCasRecord({ + let record12 = await readCasRecord({ paths: options.paths, file: cas.originalRecord, operation: options.operation, @@ -25386,7 +25135,7 @@ async function ensureOriginalTargetQuarantined(options) { expectedHash }); if (await pathExists(cas.originalQuarantine)) { - if (!record13) { + if (!record12) { throw new Error( `Archive apply quarantine ${options.operation.target} has no bound object identity` ); @@ -25395,18 +25144,18 @@ async function ensureOriginalTargetQuarantined(options) { paths: options.paths, file: cas.originalQuarantine, operation: options.operation, - record: record13, + record: record12, label: `Archive apply quarantine ${options.operation.target}` }); await ensureBackup(options.paths, options.operation, options.hooks); - return { record: record13, quarantine: cas.originalQuarantine }; + return { record: record12, quarantine: cas.originalQuarantine }; } if (!await pathExists(options.target)) { throw new Error( `Archive apply target ${options.operation.target} disappeared before quarantine` ); } - record13 ??= await bindCurrentTarget({ + record12 ??= await bindCurrentTarget({ paths: options.paths, file: options.target, recordFile: cas.originalRecord, @@ -25419,7 +25168,7 @@ async function ensureOriginalTargetQuarantined(options) { paths: options.paths, file: options.target, operation: options.operation, - record: record13, + record: record12, label: `Archive apply target ${options.operation.target}` }); await ensureBackup(options.paths, options.operation, options.hooks); @@ -25429,10 +25178,10 @@ async function ensureOriginalTargetQuarantined(options) { phase: "apply", target: options.target, quarantine: cas.originalQuarantine, - record: record13, + record: record12, hooks: options.hooks }); - return { record: record13, quarantine: cas.originalQuarantine }; + return { record: record12, quarantine: cas.originalQuarantine }; } async function ensureWriteCandidate(options) { const cas = await archiveCasPaths( @@ -25479,19 +25228,19 @@ async function ensureWriteInstalled(options) { options.operation, options.target ); - let record13 = await readCasRecord({ + let record12 = await readCasRecord({ paths: options.paths, file: cas.postRecord, operation: options.operation, role: "post", expectedHash }); - if (record13 && await pathExists(options.target)) { + if (record12 && await pathExists(options.target)) { await validateFileAgainstCasRecord({ paths: options.paths, file: options.target, operation: options.operation, - record: record13, + record: record12, label: `Archive write target ${options.operation.target}` }); if (await pathExists(cas.candidate)) { @@ -25499,11 +25248,11 @@ async function ensureWriteInstalled(options) { paths: options.paths, file: cas.candidate, operation: options.operation, - record: record13, + record: record12, label: `Archive write candidate ${options.operation.target}` }); } - return record13; + return record12; } if (await pathExists(options.target)) { const candidate2 = await ensureWriteCandidate(options); @@ -25524,7 +25273,7 @@ async function ensureWriteInstalled(options) { `Archive write target ${options.operation.target} is occupied by an external object` ); } - record13 = await persistCasRecord({ + record12 = await persistCasRecord({ paths: options.paths, file: cas.postRecord, operation: options.operation, @@ -25536,10 +25285,10 @@ async function ensureWriteInstalled(options) { paths: options.paths, file: candidate2, operation: options.operation, - record: record13, + record: record12, label: `Archive write candidate ${options.operation.target}` }); - return record13; + return record12; } const candidate = await ensureWriteCandidate(options); const candidateIdentity = await captureStableArchiveFile({ @@ -25595,7 +25344,7 @@ async function ensureWriteInstalled(options) { if (!sameFileObject2(candidateIdentity.identity, targetIdentity.identity)) { throw new Error(`Archive write target ${options.operation.target} changed during install`); } - record13 = await persistCasRecord({ + record12 = await persistCasRecord({ paths: options.paths, file: cas.postRecord, operation: options.operation, @@ -25607,10 +25356,10 @@ async function ensureWriteInstalled(options) { paths: options.paths, file: candidate, operation: options.operation, - record: record13, + record: record12, label: `Archive write candidate ${options.operation.target}` }); - return record13; + return record12; } async function applyWrite(paths, journal, operation, hooks) { const target = await resolveRef2(paths, operation.target); @@ -28105,7 +27854,7 @@ import { promises as fs31 } from "fs"; import path49 from "path"; // domains/comet-native/native-evidence-retention.ts -import { createHash as createHash18, randomUUID as randomUUID8 } from "node:crypto"; +import { createHash as createHash17, randomUUID as randomUUID8 } from "node:crypto"; import { constants as fsConstants7, promises as fs29 } from "node:fs"; import path47 from "node:path"; @@ -28115,7 +27864,7 @@ import { randomUUID as randomUUID7 } from "crypto"; import { promises as fs28 } from "fs"; import path46 from "path"; import { isDeepStrictEqual as isDeepStrictEqual3 } from "util"; -var HASH_PATTERN20 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN19 = /^[a-f0-9]{64}$/u; var MIGRATION_JOURNAL_KEYS = /* @__PURE__ */ new Set([ "schema", "id", @@ -28247,23 +27996,23 @@ function parseTransitionSupersede(value, expectedName, nextState, migrationId) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error("Schema migration transition supersede plan is invalid"); } - const record13 = value; + const record12 = value; rejectUnknownFields( - record13, + record12, MIGRATION_TRANSITION_SUPERSEDE_KEYS, "Schema migration transition supersede plan" ); - if (typeof record13.sourceHash !== "string" || !HASH_PATTERN20.test(record13.sourceHash)) { + if (typeof record12.sourceHash !== "string" || !HASH_PATTERN19.test(record12.sourceHash)) { throw new Error("Schema migration transition supersede source hash is invalid"); } - if (typeof record13.transitionId !== "string" || record13.transitionId.length === 0) { + if (typeof record12.transitionId !== "string" || record12.transitionId.length === 0) { throw new Error("Schema migration superseded transition id is invalid"); } - if (typeof record13.evidenceHash !== "string" || !HASH_PATTERN20.test(record13.evidenceHash)) { + if (typeof record12.evidenceHash !== "string" || !HASH_PATTERN19.test(record12.evidenceHash)) { throw new Error("Schema migration transition supersede evidence hash is invalid"); } - const previousRun = parseNativeStoredRunStateValue(record13.previousRun); - const nextRun = parseNativeStoredRunStateValue(record13.nextRun); + const previousRun = parseNativeStoredRunStateValue(record12.previousRun); + const nextRun = parseNativeStoredRunStateValue(record12.nextRun); if (previousRun.runId !== nextRun.runId || nextState.run_id !== nextRun.runId || nextState.phase !== "build" && nextState.phase !== "verify" || nextRun.currentStep !== nextState.phase || nextRun.pending !== null || nextRun.status !== "running" || nextState.verification_result !== "pending" || nextState.verification_report !== null || nextState.implementation_scope !== null || nextState.verification_evidence !== null || nextState.partial_allowance !== null) { throw new Error("Schema migration transition supersede Run does not match target state"); } @@ -28278,12 +28027,12 @@ function parseTransitionSupersede(value, expectedName, nextState, migrationId) { if (!expectedNextRun || !sameRunState(expectedNextRun, nextRun)) { throw new Error("Schema migration transition supersede Run retreat is invalid"); } - if (!record13.eventData || typeof record13.eventData !== "object" || Array.isArray(record13.eventData)) { + if (!record12.eventData || typeof record12.eventData !== "object" || Array.isArray(record12.eventData)) { throw new Error("Schema migration transition supersede event is invalid"); } - const eventData = record13.eventData; + const eventData = record12.eventData; const eventKeys = Object.keys(eventData); - if (eventKeys.length !== MIGRATION_SUPERSEDE_EVENT_KEYS.size || eventKeys.some((key) => !MIGRATION_SUPERSEDE_EVENT_KEYS.has(key)) || eventData.fromSchema !== NATIVE_V2_CHANGE_SCHEMA || eventData.toSchema !== NATIVE_CHANGE_SCHEMA || !["build", "verify", "archive"].includes(eventData.previousPhase) || eventData.nextPhase !== nextState.phase || eventData.reason !== (nextState.phase === "build" ? "implementation-scope-required" : "verification-evidence-required") || eventData.supersededTransitionId !== record13.transitionId) { + if (eventKeys.length !== MIGRATION_SUPERSEDE_EVENT_KEYS.size || eventKeys.some((key) => !MIGRATION_SUPERSEDE_EVENT_KEYS.has(key)) || eventData.fromSchema !== NATIVE_V2_CHANGE_SCHEMA || eventData.toSchema !== NATIVE_CHANGE_SCHEMA || !["build", "verify", "archive"].includes(eventData.previousPhase) || eventData.nextPhase !== nextState.phase || eventData.reason !== (nextState.phase === "build" ? "implementation-scope-required" : "verification-evidence-required") || eventData.supersededTransitionId !== record12.transitionId) { throw new Error("Schema migration transition supersede event semantics are invalid"); } if (nextState.name !== expectedName) { @@ -28293,7 +28042,7 @@ function parseTransitionSupersede(value, expectedName, nextState, migrationId) { JSON.stringify({ operation: "supersede-v2-evidence-transition", change: expectedName, - transitionId: record13.transitionId, + transitionId: record12.transitionId, migrationId, previousPhase: eventData.previousPhase, nextPhase: eventData.nextPhase, @@ -28302,15 +28051,15 @@ function parseTransitionSupersede(value, expectedName, nextState, migrationId) { nextRevision: nextState.revision }) ); - if (record13.evidenceHash !== expectedEvidenceHash) { + if (record12.evidenceHash !== expectedEvidenceHash) { throw new Error("Schema migration transition supersede evidence hash mismatch"); } return { - sourceHash: record13.sourceHash, - transitionId: record13.transitionId, + sourceHash: record12.sourceHash, + transitionId: record12.transitionId, previousRun, nextRun, - evidenceHash: record13.evidenceHash, + evidenceHash: record12.evidenceHash, eventData }; } @@ -28337,7 +28086,7 @@ function parseMigrationJournal(value, expectedName) { if (typeof journal.id !== "string" || journal.id.length === 0) { throw new Error("Schema migration id is invalid"); } - if (typeof journal.sourceHash !== "string" || !HASH_PATTERN20.test(journal.sourceHash) || typeof journal.targetHash !== "string" || !HASH_PATTERN20.test(journal.targetHash)) { + if (typeof journal.sourceHash !== "string" || !HASH_PATTERN19.test(journal.sourceHash) || typeof journal.targetHash !== "string" || !HASH_PATTERN19.test(journal.targetHash)) { throw new Error("Schema migration hash is invalid"); } if (typeof journal.createdAt !== "string" || Number.isNaN(Date.parse(journal.createdAt))) { @@ -28361,7 +28110,7 @@ function parseMigrationJournal(value, expectedName) { "Schema migration transition target" ); const transitionValue = journal.transition; - if (typeof transitionValue.sourceHash !== "string" || !HASH_PATTERN20.test(transitionValue.sourceHash) || typeof transitionValue.targetHash !== "string" || !HASH_PATTERN20.test(transitionValue.targetHash)) { + if (typeof transitionValue.sourceHash !== "string" || !HASH_PATTERN19.test(transitionValue.sourceHash) || typeof transitionValue.targetHash !== "string" || !HASH_PATTERN19.test(transitionValue.targetHash)) { throw new Error("Schema migration transition hash is invalid"); } const parsedNextJournal = v1ToV2 ? parseV2NativeTransitionJournalValue(transitionValue.nextJournal, expectedName) : parseNativeTransitionJournalValue(transitionValue.nextJournal, expectedName); @@ -28419,7 +28168,7 @@ function parseMigrationJournal(value, expectedName) { } catch (error) { throw new Error("Schema migration Run retreat is invalid", { cause: error }); } - if (typeof retreat.evidenceHash !== "string" || !HASH_PATTERN20.test(retreat.evidenceHash) || !retreat.eventData || typeof retreat.eventData !== "object" || Array.isArray(retreat.eventData)) { + if (typeof retreat.evidenceHash !== "string" || !HASH_PATTERN19.test(retreat.evidenceHash) || !retreat.eventData || typeof retreat.eventData !== "object" || Array.isArray(retreat.eventData)) { throw new Error("Schema migration Run retreat is invalid"); } if (nextState.schema !== NATIVE_CHANGE_SCHEMA || nextState.phase !== "build" || nextState.run_id !== nextRun.runId || previousRun.runId !== nextRun.runId || previousRun.currentStep !== "verify" && previousRun.currentStep !== "archive" || nextRun.currentStep !== "build" || nextRun.iteration !== previousRun.iteration + 1) { @@ -29112,7 +28861,7 @@ function parseDocument3(kind, hash8, value, expectedChange) { throw new Error("Native report evidence must be an object"); } const report = value; - if (Object.keys(report).sort().join(",") !== "content,reportHash,schema" || report.schema !== "comet.native.verification-report.v1" || report.reportHash !== hash8 || typeof report.content !== "string" || createHash18("sha256").update(Buffer.from(report.content, "utf8")).digest("hex") !== hash8) { + if (Object.keys(report).sort().join(",") !== "content,reportHash,schema" || report.schema !== "comet.native.verification-report.v1" || report.reportHash !== hash8 || typeof report.content !== "string" || createHash17("sha256").update(Buffer.from(report.content, "utf8")).digest("hex") !== hash8) { throw new Error("Native report evidence filename/hash does not match its content"); } return { canonical: report, dependencies: [] }; @@ -29787,7 +29536,7 @@ async function inspectNativeEvidenceRetention(options) { } // domains/comet-native/native-root-move.ts -import { createHash as createHash19, randomUUID as randomUUID9 } from "crypto"; +import { createHash as createHash18, randomUUID as randomUUID9 } from "crypto"; import { promises as fs30 } from "fs"; import path48 from "path"; var NATIVE_ROOT_MOVE_MAX_FILE_BYTES = 64 * 1024 * 1024; @@ -30000,7 +29749,7 @@ function cleanupManifestSource(manifest) { return JSON.stringify(manifest, null, 2) + "\n"; } function cleanupManifestHash(source) { - return createHash19("sha256").update(source).digest("hex"); + return createHash18("sha256").update(source).digest("hex"); } function parseCleanupManifestEntry(value, index) { if (!value || typeof value !== "object" || Array.isArray(value)) { @@ -31708,27 +31457,27 @@ async function readNativeProposedSpecs(paths, name) { } // domains/comet-native/native-review-signer.ts -var HASH_PATTERN21 = /^[a-f0-9]{64}$/u; -function record12(value, label) { +var HASH_PATTERN20 = /^[a-f0-9]{64}$/u; +function record11(value, label) { if (value === null || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } return value; } -function exactKeys10(value, keys, label) { +function exactKeys9(value, keys, label) { if (Object.keys(value).sort().join("\0") !== [...keys].sort().join("\0")) { throw new Error(`${label} has unexpected fields`); } } function hash7(value, label) { - if (typeof value !== "string" || !HASH_PATTERN21.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN20.test(value)) { throw new Error(`${label} is invalid`); } return value; } function parseNativeIndependentReviewApproval(value) { - const root = record12(value, "Native review approval"); - exactKeys10( + const root = record11(value, "Native review approval"); + exactKeys9( root, ["schema", "preparationHash", "receipt", "payloadHash"], "Native review approval" @@ -31736,8 +31485,8 @@ function parseNativeIndependentReviewApproval(value) { if (root.schema !== "comet.native.review-approval.v1") { throw new Error("Native review approval schema is invalid"); } - const receipt = record12(root.receipt, "Native review approval receipt"); - exactKeys10( + const receipt = record11(root.receipt, "Native review approval receipt"); + exactKeys9( receipt, ["kind", "role", "status", "bindings", "acceptanceIds", "actor", "issuedAt", "evidence"], "Native review approval receipt" @@ -31745,7 +31494,7 @@ function parseNativeIndependentReviewApproval(value) { if (receipt.kind !== "independent-review" || receipt.role !== "acceptance-evidence" || receipt.status !== "passed" && receipt.status !== "blocked" || !Array.isArray(receipt.acceptanceIds) || receipt.acceptanceIds.some((entry2) => typeof entry2 !== "string") || typeof receipt.actor !== "string" || typeof receipt.issuedAt !== "string") { throw new Error("Native review approval receipt is invalid"); } - const evidence = record12(receipt.evidence, "Native review approval receipt evidence"); + const evidence = record11(receipt.evidence, "Native review approval receipt evidence"); const reviewerIdentity = parseNativeReviewIdentity(evidence.reviewerIdentity); if (receipt.actor !== `review-key:${reviewerIdentity.keyId}`) { throw new Error("Native review approval actor/reviewer identity mismatch"); @@ -31782,8 +31531,8 @@ function signNativeIndependentReviewApproval(options) { }); } function parseNativeImplementationPreparation(value) { - const root = record12(value, "Native implementation preparation"); - exactKeys10( + const root = record11(value, "Native implementation preparation"); + exactKeys9( root, ["schema", "receipt", "payloadHash", "preparationHash"], "Native implementation preparation" @@ -31791,8 +31540,8 @@ function parseNativeImplementationPreparation(value) { if (root.schema !== "comet.native.implementation-preparation.v1") { throw new Error("Native implementation preparation schema is invalid"); } - const receipt = record12(root.receipt, "Native implementation preparation receipt"); - exactKeys10( + const receipt = record11(root.receipt, "Native implementation preparation receipt"); + exactKeys9( receipt, ["kind", "role", "status", "bindings", "acceptanceIds", "actor", "issuedAt", "evidence"], "Native implementation preparation receipt" @@ -32892,12 +32641,12 @@ function addTarget(targets, value) { function collectTargets(input, args) { const targets = []; const records = [args, input].filter(isRecord2); - for (const record13 of records) { - for (const key of SINGULAR_PATH_KEYS) addTarget(targets, record13[key]); - for (const key of PLURAL_PATH_KEYS) addTarget(targets, record13[key]); - for (const key of NESTED_TARGET_KEYS) addTarget(targets, record13[key]); + for (const record12 of records) { + for (const key of SINGULAR_PATH_KEYS) addTarget(targets, record12[key]); + for (const key of PLURAL_PATH_KEYS) addTarget(targets, record12[key]); + for (const key of NESTED_TARGET_KEYS) addTarget(targets, record12[key]); for (const key of PATCH_KEYS) { - const value = record13[key]; + const value = record12[key]; if (typeof value === "string") targets.push(...patchTargets(value)); } } @@ -33077,7 +32826,7 @@ Commands: init [--root ] [--language en|zh-CN] root show root move - new --creation-authorization [--language en|zh-CN] + new [--language en|zh-CN] spec remove spec rebase --summary list [--cursor ] @@ -33090,7 +32839,6 @@ Commands: trust keygen --identity --private-key trust identity --private-key-env --identity trust policy --implementation-identity --reviewer-identity --waiver-identity --controller-private-key-env - trust authorize --controller-private-key-env --output receipt manual --acceptance --responsible --step --observation --confirmed receipt automated --acceptance [--timeout-ms ] -- [args...] receipt implement prepare --identity --output @@ -33402,10 +33150,6 @@ async function dispatch(rawArgs, explicitProjectRoot) { } if (command === "new") { const name = requiredPositional(rawArgs, "change name"); - const creationAuthorizationPath = takeOption(rawArgs, "--creation-authorization"); - if (!creationAuthorizationPath) { - throw new NativeUsageError("--creation-authorization is required"); - } let config = await readProjectConfig(projectRoot); const language = languageOption(rawArgs, config?.native.language ?? "en"); assertNoArguments(rawArgs); @@ -33422,11 +33166,7 @@ async function dispatch(rawArgs, explicitProjectRoot) { const state = await createNativeChange({ paths, name, - language, - verificationProtocol: "signed-v2", - creationAuthorization: parseNativeCreationAuthorization( - await readEvidenceJson(creationAuthorizationPath, "Native creation authorization") - ) + language }); await selectNativeChange(paths, state.name); const status = await inspectNativeStatus(paths, state.name, { @@ -33762,13 +33502,6 @@ async function dispatch(rawArgs, explicitProjectRoot) { const policyPath = path54.join(projectRoot, ...NATIVE_REVIEW_TRUST_POLICY_REF.split("/")); await withNativeMutationLock(paths, "create review trust policy", async () => { await resolveContainedNativePath(projectRoot, policyPath); - const activeEntries = await fs36.readdir(paths.changesDir).catch((error) => { - if (error.code === "ENOENT") return []; - throw error; - }); - if (activeEntries.length > 0) { - throw new Error("Native review trust policy must be created before any active change"); - } await atomicWriteJson(policyPath, policy, { containedRoot: projectRoot, exclusive: true @@ -33778,42 +33511,6 @@ async function dispatch(rawArgs, explicitProjectRoot) { "trust policy", { ref: NATIVE_REVIEW_TRUST_POLICY_REF, policy }, `Native review trust policy created: ${NATIVE_REVIEW_TRUST_POLICY_REF} -` - ); - } - if (subcommand === "authorize") { - const name = requiredPositional(rawArgs, "change name"); - const controllerPrivateKeyEnv = takeOption(rawArgs, "--controller-private-key-env"); - const outputPathValue = takeOption(rawArgs, "--output"); - if (!controllerPrivateKeyEnv || !outputPathValue) { - throw new NativeUsageError( - "trust authorize requires --controller-private-key-env and --output" - ); - } - assertNoArguments(rawArgs); - const { paths } = await configuredPaths(projectRoot); - const [controllerTrust, policy, projectRootHash] = await Promise.all([ - readNativeControllerTrustProject(projectRoot), - readNativeReviewTrustPolicy(paths), - nativeControllerProjectRootHash(projectRoot) - ]); - if (!controllerTrust) { - throw new Error("Native project has no controller-owned trust root"); - } - const authorization = buildNativeCreationAuthorization({ - controllerIdentity: controllerTrust.controllerIdentity, - controllerPrivateKey: privateKeyFromEnvironment(controllerPrivateKeyEnv), - projectRootHash, - policyHash: policy.policyHash, - change: name - }); - const outputPath = path54.resolve(outputPathValue); - await writeExclusiveFile(outputPath, `${JSON.stringify(authorization, null, 2)} -`, 420); - return success( - "trust authorize", - { outputPath, authorization }, - `Native creation authorization written to ${outputPath} ` ); } diff --git a/assets/skills/comet/reference/classic-layout.md b/assets/skills/comet/reference/classic-layout.md index a3d2d6ae..9e24a301 100644 --- a/assets/skills/comet/reference/classic-layout.md +++ b/assets/skills/comet/reference/classic-layout.md @@ -29,6 +29,6 @@ Accept only `schema: comet.classic-layout.v1`. Bind the returned `openSpecRoot`, ## New, existing, and migrated projects - New Classic projects default to `docs/openspec/`. -- Existing projects without `classic.artifact_layout` continue to use `openspec/`. +- A missing `classic.artifact_layout` defaults to `docs/openspec/`. When `comet update` detects existing root-level `openspec/` artifacts, it explicitly backfills `legacy` without moving them. - Normal init/update never moves existing artifacts. Run `comet classic root move docs --dry-run` first and retain its plan ID; use `comet classic root move docs --apply --plan ` only with explicit user authorization. - The first migration version rejects every active or unmanaged OpenSpec change. diff --git a/assets/skills/comet/scripts/comet-entry-runtime.mjs b/assets/skills/comet/scripts/comet-entry-runtime.mjs index 9b03ac77..fd4cf9f7 100644 --- a/assets/skills/comet/scripts/comet-entry-runtime.mjs +++ b/assets/skills/comet/scripts/comet-entry-runtime.mjs @@ -7406,7 +7406,7 @@ function normalizeWorkflowArtifactRoot(value) { const segments = projectRelativeSegments(value, "native.artifact_root"); return segments.length === 0 ? "." : segments.join("/"); } -function normalizeClassicArtifactLayout(value, fallback = "legacy") { +function normalizeClassicArtifactLayout(value, fallback = "docs") { const resolved = value ?? fallback; if (resolved !== "legacy" && resolved !== "docs") { throw new Error("classic.artifact_layout must be legacy or docs"); diff --git a/assets/skills/comet/scripts/comet-hook-router.mjs b/assets/skills/comet/scripts/comet-hook-router.mjs index 112b3a05..963c97f7 100644 --- a/assets/skills/comet/scripts/comet-hook-router.mjs +++ b/assets/skills/comet/scripts/comet-hook-router.mjs @@ -126,17 +126,17 @@ var require_visit = __commonJS({ visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; - function visit_(key, node, visitor, path21) { - const ctrl = callVisitor(key, node, visitor, path21); + function visit_(key, node, visitor, path19) { + const ctrl = callVisitor(key, node, visitor, path19); if (identity.isNode(ctrl) || identity.isPair(ctrl)) { - replaceNode(key, path21, ctrl); - return visit_(key, ctrl, visitor, path21); + replaceNode(key, path19, ctrl); + return visit_(key, ctrl, visitor, path19); } if (typeof ctrl !== "symbol") { if (identity.isCollection(node)) { - path21 = Object.freeze(path21.concat(node)); + path19 = Object.freeze(path19.concat(node)); for (let i = 0; i < node.items.length; ++i) { - const ci = visit_(i, node.items[i], visitor, path21); + const ci = visit_(i, node.items[i], visitor, path19); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK) @@ -147,13 +147,13 @@ var require_visit = __commonJS({ } } } else if (identity.isPair(node)) { - path21 = Object.freeze(path21.concat(node)); - const ck = visit_("key", node.key, visitor, path21); + path19 = Object.freeze(path19.concat(node)); + const ck = visit_("key", node.key, visitor, path19); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; - const cv = visit_("value", node.value, visitor, path21); + const cv = visit_("value", node.value, visitor, path19); if (cv === BREAK) return BREAK; else if (cv === REMOVE) @@ -174,17 +174,17 @@ var require_visit = __commonJS({ visitAsync.BREAK = BREAK; visitAsync.SKIP = SKIP; visitAsync.REMOVE = REMOVE; - async function visitAsync_(key, node, visitor, path21) { - const ctrl = await callVisitor(key, node, visitor, path21); + async function visitAsync_(key, node, visitor, path19) { + const ctrl = await callVisitor(key, node, visitor, path19); if (identity.isNode(ctrl) || identity.isPair(ctrl)) { - replaceNode(key, path21, ctrl); - return visitAsync_(key, ctrl, visitor, path21); + replaceNode(key, path19, ctrl); + return visitAsync_(key, ctrl, visitor, path19); } if (typeof ctrl !== "symbol") { if (identity.isCollection(node)) { - path21 = Object.freeze(path21.concat(node)); + path19 = Object.freeze(path19.concat(node)); for (let i = 0; i < node.items.length; ++i) { - const ci = await visitAsync_(i, node.items[i], visitor, path21); + const ci = await visitAsync_(i, node.items[i], visitor, path19); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK) @@ -195,13 +195,13 @@ var require_visit = __commonJS({ } } } else if (identity.isPair(node)) { - path21 = Object.freeze(path21.concat(node)); - const ck = await visitAsync_("key", node.key, visitor, path21); + path19 = Object.freeze(path19.concat(node)); + const ck = await visitAsync_("key", node.key, visitor, path19); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; - const cv = await visitAsync_("value", node.value, visitor, path21); + const cv = await visitAsync_("value", node.value, visitor, path19); if (cv === BREAK) return BREAK; else if (cv === REMOVE) @@ -228,23 +228,23 @@ var require_visit = __commonJS({ } return visitor; } - function callVisitor(key, node, visitor, path21) { + function callVisitor(key, node, visitor, path19) { if (typeof visitor === "function") - return visitor(key, node, path21); + return visitor(key, node, path19); if (identity.isMap(node)) - return visitor.Map?.(key, node, path21); + return visitor.Map?.(key, node, path19); if (identity.isSeq(node)) - return visitor.Seq?.(key, node, path21); + return visitor.Seq?.(key, node, path19); if (identity.isPair(node)) - return visitor.Pair?.(key, node, path21); + return visitor.Pair?.(key, node, path19); if (identity.isScalar(node)) - return visitor.Scalar?.(key, node, path21); + return visitor.Scalar?.(key, node, path19); if (identity.isAlias(node)) - return visitor.Alias?.(key, node, path21); + return visitor.Alias?.(key, node, path19); return void 0; } - function replaceNode(key, path21, node) { - const parent = path21[path21.length - 1]; + function replaceNode(key, path19, node) { + const parent = path19[path19.length - 1]; if (identity.isCollection(parent)) { parent.items[key] = node; } else if (identity.isPair(parent)) { @@ -854,10 +854,10 @@ var require_Collection = __commonJS({ var createNode = require_createNode(); var identity = require_identity(); var Node = require_Node(); - function collectionFromPath(schema, path21, value) { + function collectionFromPath(schema, path19, value) { let v = value; - for (let i = path21.length - 1; i >= 0; --i) { - const k = path21[i]; + for (let i = path19.length - 1; i >= 0; --i) { + const k = path19[i]; if (typeof k === "number" && Number.isInteger(k) && k >= 0) { const a = []; a[k] = v; @@ -876,7 +876,7 @@ var require_Collection = __commonJS({ sourceObjects: /* @__PURE__ */ new Map() }); } - var isEmptyPath = (path21) => path21 == null || typeof path21 === "object" && !!path21[Symbol.iterator]().next().done; + var isEmptyPath = (path19) => path19 == null || typeof path19 === "object" && !!path19[Symbol.iterator]().next().done; var Collection = class extends Node.NodeBase { constructor(type, schema) { super(type); @@ -906,11 +906,11 @@ var require_Collection = __commonJS({ * be a Pair instance or a `{ key, value }` object, which may not have a key * that already exists in the map. */ - addIn(path21, value) { - if (isEmptyPath(path21)) + addIn(path19, value) { + if (isEmptyPath(path19)) this.add(value); else { - const [key, ...rest] = path21; + const [key, ...rest] = path19; const node = this.get(key, true); if (identity.isCollection(node)) node.addIn(rest, value); @@ -924,8 +924,8 @@ var require_Collection = __commonJS({ * Removes a value from the collection. * @returns `true` if the item was found and removed. */ - deleteIn(path21) { - const [key, ...rest] = path21; + deleteIn(path19) { + const [key, ...rest] = path19; if (rest.length === 0) return this.delete(key); const node = this.get(key, true); @@ -939,8 +939,8 @@ var require_Collection = __commonJS({ * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path21, keepScalar) { - const [key, ...rest] = path21; + getIn(path19, keepScalar) { + const [key, ...rest] = path19; const node = this.get(key, true); if (rest.length === 0) return !keepScalar && identity.isScalar(node) ? node.value : node; @@ -958,8 +958,8 @@ var require_Collection = __commonJS({ /** * Checks if the collection includes a value with the key `key`. */ - hasIn(path21) { - const [key, ...rest] = path21; + hasIn(path19) { + const [key, ...rest] = path19; if (rest.length === 0) return this.has(key); const node = this.get(key, true); @@ -969,8 +969,8 @@ var require_Collection = __commonJS({ * Sets a value in this collection. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - setIn(path21, value) { - const [key, ...rest] = path21; + setIn(path19, value) { + const [key, ...rest] = path19; if (rest.length === 0) { this.set(key, value); } else { @@ -3485,9 +3485,9 @@ var require_Document = __commonJS({ this.contents.add(value); } /** Adds a value to the document. */ - addIn(path21, value) { + addIn(path19, value) { if (assertCollection(this.contents)) - this.contents.addIn(path21, value); + this.contents.addIn(path19, value); } /** * Create a new `Alias` node, ensuring that the target `node` has the required anchor. @@ -3562,14 +3562,14 @@ var require_Document = __commonJS({ * Removes a value from the document. * @returns `true` if the item was found and removed. */ - deleteIn(path21) { - if (Collection.isEmptyPath(path21)) { + deleteIn(path19) { + if (Collection.isEmptyPath(path19)) { if (this.contents == null) return false; this.contents = null; return true; } - return assertCollection(this.contents) ? this.contents.deleteIn(path21) : false; + return assertCollection(this.contents) ? this.contents.deleteIn(path19) : false; } /** * Returns item at `key`, or `undefined` if not found. By default unwraps @@ -3584,10 +3584,10 @@ var require_Document = __commonJS({ * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path21, keepScalar) { - if (Collection.isEmptyPath(path21)) + getIn(path19, keepScalar) { + if (Collection.isEmptyPath(path19)) return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents; - return identity.isCollection(this.contents) ? this.contents.getIn(path21, keepScalar) : void 0; + return identity.isCollection(this.contents) ? this.contents.getIn(path19, keepScalar) : void 0; } /** * Checks if the document includes a value with the key `key`. @@ -3598,10 +3598,10 @@ var require_Document = __commonJS({ /** * Checks if the document includes a value at `path`. */ - hasIn(path21) { - if (Collection.isEmptyPath(path21)) + hasIn(path19) { + if (Collection.isEmptyPath(path19)) return this.contents !== void 0; - return identity.isCollection(this.contents) ? this.contents.hasIn(path21) : false; + return identity.isCollection(this.contents) ? this.contents.hasIn(path19) : false; } /** * Sets a value in this document. For `!!set`, `value` needs to be a @@ -3618,13 +3618,13 @@ var require_Document = __commonJS({ * Sets a value in this document. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - setIn(path21, value) { - if (Collection.isEmptyPath(path21)) { + setIn(path19, value) { + if (Collection.isEmptyPath(path19)) { this.contents = value; } else if (this.contents == null) { - this.contents = Collection.collectionFromPath(this.schema, Array.from(path21), value); + this.contents = Collection.collectionFromPath(this.schema, Array.from(path19), value); } else if (assertCollection(this.contents)) { - this.contents.setIn(path21, value); + this.contents.setIn(path19, value); } } /** @@ -5584,9 +5584,9 @@ var require_cst_visit = __commonJS({ visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; - visit.itemAtPath = (cst, path21) => { + visit.itemAtPath = (cst, path19) => { let item = cst; - for (const [field2, index] of path21) { + for (const [field2, index] of path19) { const tok = item?.[field2]; if (tok && "items" in tok) { item = tok.items[index]; @@ -5595,23 +5595,23 @@ var require_cst_visit = __commonJS({ } return item; }; - visit.parentCollection = (cst, path21) => { - const parent = visit.itemAtPath(cst, path21.slice(0, -1)); - const field2 = path21[path21.length - 1][0]; + visit.parentCollection = (cst, path19) => { + const parent = visit.itemAtPath(cst, path19.slice(0, -1)); + const field2 = path19[path19.length - 1][0]; const coll = parent?.[field2]; if (coll && "items" in coll) return coll; throw new Error("Parent collection not found"); }; - function _visit(path21, item, visitor) { - let ctrl = visitor(item, path21); + function _visit(path19, item, visitor) { + let ctrl = visitor(item, path19); if (typeof ctrl === "symbol") return ctrl; for (const field2 of ["key", "value"]) { const token = item[field2]; if (token && "items" in token) { for (let i = 0; i < token.items.length; ++i) { - const ci = _visit(Object.freeze(path21.concat([[field2, i]])), token.items[i], visitor); + const ci = _visit(Object.freeze(path19.concat([[field2, i]])), token.items[i], visitor); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK) @@ -5622,10 +5622,10 @@ var require_cst_visit = __commonJS({ } } if (typeof ctrl === "function" && field2 === "key") - ctrl = ctrl(item, path21); + ctrl = ctrl(item, path19); } } - return typeof ctrl === "function" ? ctrl(item, path21) : ctrl; + return typeof ctrl === "function" ? ctrl(item, path19) : ctrl; } exports.visit = visit; } @@ -6927,14 +6927,14 @@ var require_parser = __commonJS({ case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": { - const fs16 = this.flowScalar(this.type); + const fs14 = this.flowScalar(this.type); if (atNextItem || it.value) { - map.items.push({ start, key: fs16, sep: [] }); + map.items.push({ start, key: fs14, sep: [] }); this.onKeyLine = true; } else if (it.sep) { - this.stack.push(fs16); + this.stack.push(fs14); } else { - Object.assign(it, { key: fs16, sep: [] }); + Object.assign(it, { key: fs14, sep: [] }); this.onKeyLine = true; } return; @@ -7062,13 +7062,13 @@ var require_parser = __commonJS({ case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": { - const fs16 = this.flowScalar(this.type); + const fs14 = this.flowScalar(this.type); if (!it || it.value) - fc.items.push({ start: [], key: fs16, sep: [] }); + fc.items.push({ start: [], key: fs14, sep: [] }); else if (it.sep) - this.stack.push(fs16); + this.stack.push(fs14); else - Object.assign(it, { key: fs16, sep: [] }); + Object.assign(it, { key: fs14, sep: [] }); return; } case "flow-map-end": @@ -7398,7 +7398,7 @@ function normalizeWorkflowArtifactRoot(value) { const segments = projectRelativeSegments(value, "native.artifact_root"); return segments.length === 0 ? "." : segments.join("/"); } -function normalizeClassicArtifactLayout(value, fallback = "legacy") { +function normalizeClassicArtifactLayout(value, fallback = "docs") { const resolved = value ?? fallback; if (resolved !== "legacy" && resolved !== "docs") { throw new Error("classic.artifact_layout must be legacy or docs"); @@ -8475,8 +8475,8 @@ var init_state = __esm({ }); // domains/comet-entry/hook-router-entry.ts -import path20 from "path"; -import { promises as fs15 } from "fs"; +import path18 from "path"; +import { promises as fs13 } from "fs"; // domains/comet-native/native-paths.ts init_project_config(); @@ -8939,12 +8939,12 @@ function addTarget(targets, value) { function collectTargets(input, args) { const targets = []; const records = [args, input].filter(isRecord); - for (const record7 of records) { - for (const key of SINGULAR_PATH_KEYS) addTarget(targets, record7[key]); - for (const key of PLURAL_PATH_KEYS) addTarget(targets, record7[key]); - for (const key of NESTED_TARGET_KEYS) addTarget(targets, record7[key]); + for (const record3 of records) { + for (const key of SINGULAR_PATH_KEYS) addTarget(targets, record3[key]); + for (const key of PLURAL_PATH_KEYS) addTarget(targets, record3[key]); + for (const key of NESTED_TARGET_KEYS) addTarget(targets, record3[key]); for (const key of PATCH_KEYS) { - const value = record7[key]; + const value = record3[key]; if (typeof value === "string") targets.push(...patchTargets(value)); } } @@ -9147,9 +9147,9 @@ async function resolveBranchBinding(changeDir, options) { if (document.errors.length > 0) { throw new Error(`Invalid .comet.yaml: ${document.errors[0].message}`); } - const record7 = document.toJS() ?? {}; - const isolation = typeof record7.isolation === "string" ? record7.isolation : null; - const boundBranch = typeof record7.bound_branch === "string" && record7.bound_branch !== "" ? record7.bound_branch : null; + const record3 = document.toJS() ?? {}; + const isolation = typeof record3.isolation === "string" ? record3.isolation : null; + const boundBranch = typeof record3.bound_branch === "string" && record3.bound_branch !== "" ? record3.bound_branch : null; const bindingRequired = requiresBranchBinding(isolation); const currentBranch = liveGitBranch(options.cwd); const gitWorkTree = bindingRequired && boundBranch === null && currentBranch === null ? isGitWorkTree(options.cwd) : true; @@ -10232,14 +10232,13 @@ async function inspectClassicHookGuard(projectRoot, changeName, request) { } // domains/comet-native/native-hook-guard.ts -import { promises as fs14 } from "fs"; -import path19 from "path"; +import { promises as fs12 } from "fs"; +import path17 from "path"; // domains/comet-native/native-change.ts var import_yaml4 = __toESM(require_dist(), 1); -import { createHash as createHash7 } from "node:crypto"; -import { promises as fs13 } from "fs"; -import path18 from "path"; +import { promises as fs11 } from "fs"; +import path16 from "path"; // domains/comet-native/native-bounded-file.ts import { createHash as createHash2 } from "node:crypto"; @@ -10451,528 +10450,6 @@ async function readNativeBoundedTextFile(options) { } } -// domains/comet-native/native-canonical-hash.ts -import { createHash as createHash3 } from "crypto"; -function invalidCanonicalJson(detail) { - throw new TypeError(`Value is not valid canonical JSON: ${detail}`); -} -function canonicalArray(value, ancestors) { - if (ancestors.has(value)) invalidCanonicalJson("cyclic structures are not supported"); - ancestors.add(value); - try { - const enumerableKeys = Object.keys(value); - for (let index = 0; index < value.length; index += 1) { - if (!Object.prototype.hasOwnProperty.call(value, index)) { - invalidCanonicalJson("sparse arrays are not supported"); - } - } - if (enumerableKeys.length !== value.length || enumerableKeys.some((key, index) => key !== String(index))) { - invalidCanonicalJson("arrays must not have named enumerable properties"); - } - if (Object.getOwnPropertySymbols(value).length > 0) { - invalidCanonicalJson("symbol properties are not supported"); - } - return `[${value.map((entry) => canonicalValue(entry, ancestors)).join(",")}]`; - } finally { - ancestors.delete(value); - } -} -function canonicalObject(value, ancestors) { - const prototype = Object.getPrototypeOf(value); - if (prototype !== Object.prototype && prototype !== null) { - invalidCanonicalJson("only plain objects are supported"); - } - if (ancestors.has(value)) invalidCanonicalJson("cyclic structures are not supported"); - if (Object.getOwnPropertySymbols(value).length > 0) { - invalidCanonicalJson("symbol properties are not supported"); - } - ancestors.add(value); - try { - const descriptors = Object.getOwnPropertyDescriptors(value); - const keys = Object.keys(value).sort(); - const fields = keys.map((key) => { - const descriptor = descriptors[key]; - if (!descriptor || !("value" in descriptor)) { - invalidCanonicalJson("accessor properties are not supported"); - } - return `${JSON.stringify(key)}:${canonicalValue(descriptor.value, ancestors)}`; - }); - return `{${fields.join(",")}}`; - } finally { - ancestors.delete(value); - } -} -function canonicalValue(value, ancestors) { - if (value === null) return "null"; - switch (typeof value) { - case "boolean": - return value ? "true" : "false"; - case "string": - return JSON.stringify(value); - case "number": - if (!Number.isFinite(value)) invalidCanonicalJson("numbers must be finite"); - return JSON.stringify(Object.is(value, -0) ? 0 : value); - case "object": - return Array.isArray(value) ? canonicalArray(value, ancestors) : canonicalObject(value, ancestors); - case "bigint": - case "function": - case "symbol": - case "undefined": - return invalidCanonicalJson(`${typeof value} values are not supported`); - } - return invalidCanonicalJson("unsupported value type"); -} -function canonicalJson(value) { - return canonicalValue(value, /* @__PURE__ */ new Set()); -} -function canonicalHash(tag, value) { - if (tag.length === 0) throw new TypeError("Canonical hash tag must be non-empty"); - if (/[\r\n]/u.test(tag)) { - throw new TypeError("Canonical hash tag must not contain a line break"); - } - return createHash3("sha256").update(`${tag} -${canonicalJson(value)}`).digest("hex"); -} - -// domains/comet-native/native-controller-trust.ts -init_race_safe_read(); -import { promises as fs11 } from "node:fs"; -import os2 from "node:os"; -import path15 from "node:path"; - -// platform/fs/trusted-readonly-file.ts -import { constants as fsConstants2, promises as fs10 } from "node:fs"; -import path14 from "node:path"; -function trustedReadonlyPosixFactsIssue(facts) { - if (facts.fileUid === facts.currentUid) { - return "Trusted file must be owned by a different host identity"; - } - if ((facts.fileMode & 18) !== 0 || facts.fileWritable) { - return "Trusted file is writable by the current process"; - } - if (facts.parents.some( - (parent) => parent.uid === facts.currentUid || (parent.mode & 18) !== 0 || parent.writable - )) { - return "Trusted file parent chain is writable by the current process"; - } - return null; -} -var testHostIsolatedFiles = /* @__PURE__ */ new Set(); -function sameIdentity(left, right) { - return left.realPath === right.realPath && left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs; -} -async function currentProcessCanWrite(file) { - try { - await fs10.access(file, fsConstants2.W_OK); - return true; - } catch (error) { - if (error.code === "EACCES" || error.code === "EPERM") { - return false; - } - throw error; - } -} -async function inspectIdentity(file) { - const [stat, realPath] = await Promise.all([fs10.lstat(file), fs10.realpath(file)]); - if (!stat.isFile() || stat.isSymbolicLink()) { - throw new Error("Trusted file capability requires a regular non-symlink file"); - } - return { - realPath, - dev: stat.dev, - ino: stat.ino, - size: stat.size, - mtimeMs: stat.mtimeMs - }; -} -async function assertTrustedReadonlyFile(options) { - const identity = await inspectIdentity(options.file); - if (options.previous && !sameIdentity(options.previous, identity)) { - throw new Error("Trusted file identity changed while reading"); - } - if (process.env.NODE_ENV === "test" && testHostIsolatedFiles.has(path14.resolve(options.file))) { - return identity; - } - if (process.platform === "win32") { - throw new Error( - "Trusted file isolation cannot be proven from Windows file mode; use a host read-only mount capability" - ); - } - const currentUid = process.geteuid?.() ?? process.getuid?.(); - if (currentUid === void 0) { - throw new Error("Trusted file owner isolation is unavailable on this platform"); - } - const fileStat = await fs10.stat(identity.realPath); - const parents = []; - let directory = path14.dirname(identity.realPath); - for (; ; ) { - const stat = await fs10.lstat(directory); - if (!stat.isDirectory() || stat.isSymbolicLink()) { - throw new Error("Trusted file parent chain is not a physical directory chain"); - } - parents.push({ - uid: stat.uid, - mode: stat.mode, - writable: await currentProcessCanWrite(directory) - }); - const parent = path14.dirname(directory); - if (parent === directory) break; - directory = parent; - } - const issue = trustedReadonlyPosixFactsIssue({ - currentUid, - fileUid: fileStat.uid, - fileMode: fileStat.mode, - fileWritable: await currentProcessCanWrite(identity.realPath), - parents - }); - if (issue) throw new Error(issue); - return identity; -} - -// domains/comet-native/native-review-identity.ts -import { - createHash as createHash4, - createPrivateKey, - createPublicKey, - generateKeyPairSync, - sign as cryptoSign, - verify as cryptoVerify -} from "node:crypto"; -var NATIVE_REVIEW_IDENTITY_SCHEMA = "comet.native.review-identity.v1"; -var NATIVE_REVIEW_SIGNATURE_SCHEMA = "comet.native.review-signature.v1"; -var ALGORITHM = "ed25519"; -var HASH_PATTERN = /^[a-f0-9]{64}$/u; -var MAX_PUBLIC_KEY_TEXT = 512; -var MAX_SIGNATURE_TEXT = 256; -var SIGNATURE_CONTEXT = Buffer.from("comet.native.review-payload.v1\0", "utf8"); -function record(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); - } - return value; -} -function exactKeys(value, expected, label) { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - if (JSON.stringify(actual) !== JSON.stringify(canonical)) { - throw new Error(`${label} fields are invalid`); - } -} -function sha2562(value) { - return createHash4("sha256").update(value).digest("hex"); -} -function payloadHash(value, label = "Native review payloadHash") { - if (typeof value !== "string" || !HASH_PATTERN.test(value)) { - throw new Error(`${label} must be a lowercase SHA-256 hash`); - } - return value; -} -function canonicalBase64(value, label, maxCharacters, expectedBytes) { - if (typeof value !== "string" || value.length === 0 || value.length > maxCharacters || value.length % 4 !== 0) { - throw new Error(`${label} is invalid`); - } - const bytes = Buffer.from(value, "base64"); - if (bytes.length === 0 || bytes.toString("base64") !== value || expectedBytes !== void 0 && bytes.length !== expectedBytes) { - throw new Error(`${label} must use canonical base64`); - } - return bytes; -} -function publicKeyMaterial(value) { - const supplied = canonicalBase64(value, "Native review public key", MAX_PUBLIC_KEY_TEXT); - let key; - try { - key = createPublicKey({ key: supplied, format: "der", type: "spki" }); - } catch (error) { - throw new Error("Native review public key is invalid", { cause: error }); - } - if (key.type !== "public" || key.asymmetricKeyType !== "ed25519") { - throw new Error("Native review public key must be Ed25519"); - } - const der = key.export({ format: "der", type: "spki" }); - if (!Buffer.isBuffer(der) || !supplied.equals(der)) { - throw new Error("Native review public key must use canonical SPKI DER"); - } - return { key, der, text: der.toString("base64") }; -} -function signaturePayload(hash) { - return Buffer.concat([SIGNATURE_CONTEXT, Buffer.from(hash, "hex")]); -} -function parseNativeReviewSignature(value) { - const root = record(value, "Native review signature"); - exactKeys( - root, - ["schema", "algorithm", "keyId", "payloadHash", "signature"], - "Native review signature" - ); - if (root.schema !== NATIVE_REVIEW_SIGNATURE_SCHEMA || root.algorithm !== ALGORITHM || typeof root.keyId !== "string" || !HASH_PATTERN.test(root.keyId)) { - throw new Error("Native review signature identity is invalid"); - } - const hash = payloadHash(root.payloadHash); - const signature = canonicalBase64( - root.signature, - "Native review signature", - MAX_SIGNATURE_TEXT, - 64 - ).toString("base64"); - return { - schema: NATIVE_REVIEW_SIGNATURE_SCHEMA, - algorithm: ALGORITHM, - keyId: root.keyId, - payloadHash: hash, - signature - }; -} -function parseNativeReviewIdentity(value) { - const root = record(value, "Native review identity"); - exactKeys(root, ["schema", "algorithm", "keyId", "publicKey"], "Native review identity"); - if (root.schema !== NATIVE_REVIEW_IDENTITY_SCHEMA || root.algorithm !== ALGORITHM || typeof root.keyId !== "string" || !HASH_PATTERN.test(root.keyId)) { - throw new Error("Native review identity keyId is invalid"); - } - const publicKey = publicKeyMaterial(root.publicKey); - const keyId = sha2562(publicKey.der); - if (root.keyId !== keyId) { - throw new Error("Native review identity keyId does not match its public key"); - } - return { - schema: NATIVE_REVIEW_IDENTITY_SCHEMA, - algorithm: ALGORITHM, - keyId, - publicKey: publicKey.text - }; -} -function verifyNativeReviewPayloadHash(options) { - const identity = parseNativeReviewIdentity(options.identity); - const hash = payloadHash(options.payloadHash); - const proof = parseNativeReviewSignature(options.proof); - if (proof.payloadHash !== hash) { - throw new Error("Native review signature payloadHash does not match the expected payloadHash"); - } - if (proof.keyId !== identity.keyId) { - throw new Error("Native review signature keyId does not match the public identity"); - } - const signature = canonicalBase64( - proof.signature, - "Native review signature", - MAX_SIGNATURE_TEXT, - 64 - ); - const publicKey = publicKeyMaterial(identity.publicKey); - if (!cryptoVerify(null, signaturePayload(hash), publicKey.key, signature)) { - throw new Error("Native review signature is invalid"); - } - return proof; -} - -// domains/comet-native/native-controller-trust.ts -var NATIVE_CONTROLLER_TRUST_STORE_SCHEMA = "comet.native.controller-trust-store.v1"; -var NATIVE_CONTROLLER_TRUST_STORE_TEST_ENV = "COMET_NATIVE_CONTROLLER_TRUST_STORE_TEST_PATH"; -var PROJECT_ROOT_HASH_TAG = "comet.native.controller-project-root.v1"; -var MAX_STORE_BYTES = 256 * 1024; -var HASH_PATTERN2 = /^[a-f0-9]{64}$/u; -var CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; -function record2(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); - } - return value; -} -function exactKeys2(value, keys, label) { - const actual = Object.keys(value).sort(); - const expected = [...keys].sort(); - if (JSON.stringify(actual) !== JSON.stringify(expected)) { - throw new Error(`${label} fields are invalid`); - } -} -function isInside4(parent, target) { - const relative = path15.relative(parent, target); - return relative === "" || !path15.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path15.sep}`); -} -function normalizedPhysicalRoot(root) { - const normalized2 = path15.normalize(root).replaceAll("\\", "/"); - return process.platform === "win32" ? normalized2.toLowerCase() : normalized2; -} -async function nativeControllerProjectRootHash(projectRoot) { - return canonicalHash( - PROJECT_ROOT_HASH_TAG, - normalizedPhysicalRoot(await fs11.realpath(projectRoot)) - ); -} -function parseNativeControllerTrustStore(value) { - const root = record2(value, "Native controller trust store"); - exactKeys2(root, ["schema", "projects"], "Native controller trust store"); - if (root.schema !== NATIVE_CONTROLLER_TRUST_STORE_SCHEMA || !Array.isArray(root.projects) || root.projects.length === 0 || root.projects.length > 1024) { - throw new Error("Native controller trust store is invalid"); - } - const projects = root.projects.map((value2, index) => { - const project = record2(value2, `Native controller trust project ${index}`); - exactKeys2( - project, - ["projectRootHash", "controllerIdentity", "legacyChanges"], - `Native controller trust project ${index}` - ); - if (typeof project.projectRootHash !== "string" || !HASH_PATTERN2.test(project.projectRootHash) || !Array.isArray(project.legacyChanges)) { - throw new Error(`Native controller trust project ${index} is invalid`); - } - const legacyChanges = project.legacyChanges.map((change) => { - if (typeof change !== "string" || !CHANGE_NAME_PATTERN.test(change)) { - throw new Error(`Native controller trust project ${index} legacy change is invalid`); - } - return change; - }); - if (JSON.stringify(legacyChanges) !== JSON.stringify( - [...new Set(legacyChanges)].sort((left, right) => left.localeCompare(right, "en")) - )) { - throw new Error(`Native controller trust project ${index} legacy changes must be sorted`); - } - return { - projectRootHash: project.projectRootHash, - controllerIdentity: parseNativeReviewIdentity(project.controllerIdentity), - legacyChanges - }; - }); - if (JSON.stringify(projects.map((project) => project.projectRootHash)) !== JSON.stringify( - [...new Set(projects.map((project) => project.projectRootHash))].sort( - (left, right) => left.localeCompare(right, "en") - ) - )) { - throw new Error("Native controller trust projects must be sorted and unique"); - } - return { schema: NATIVE_CONTROLLER_TRUST_STORE_SCHEMA, projects }; -} -function nativeControllerTrustStorePath() { - const testPath = process.env.NODE_ENV === "test" ? process.env[NATIVE_CONTROLLER_TRUST_STORE_TEST_ENV] : void 0; - return path15.resolve( - testPath ?? path15.join(os2.homedir(), ".comet", "native-controller-trust.json") - ); -} -async function readNativeControllerTrustProject(projectRoot) { - const storePath = nativeControllerTrustStorePath(); - let physicalProjectRoot; - try { - physicalProjectRoot = await fs11.realpath(projectRoot); - } catch (error) { - throw new Error("Native project root is unavailable for controller trust", { cause: error }); - } - let trustedIdentity; - try { - trustedIdentity = await assertTrustedReadonlyFile({ file: storePath }); - } catch (error) { - if (error.code === "ENOENT") return null; - throw new Error("Native controller trust store is not host-isolated read-only", { - cause: error - }); - } - let result2; - try { - result2 = await readFileRaceSafe(storePath, MAX_STORE_BYTES, { - label: "Native controller trust store", - verify: (_checkpoint, context) => { - if (isInside4(physicalProjectRoot, context.realPath)) { - throw new Error("Native controller trust store must resolve outside the project"); - } - } - }); - } catch (error) { - if (error.code === "ENOENT") return null; - throw error; - } - try { - await assertTrustedReadonlyFile({ - file: storePath, - previous: trustedIdentity - }); - } catch (error) { - throw new Error("Native controller trust store isolation changed while reading", { - cause: error - }); - } - let parsed; - try { - parsed = parseNativeControllerTrustStore(JSON.parse(result2.bytes.toString("utf8"))); - } catch (error) { - throw new Error("Native controller trust store is not valid canonical JSON", { - cause: error - }); - } - const projectRootHash = await nativeControllerProjectRootHash(projectRoot); - return parsed.projects.find((project) => project.projectRootHash === projectRootHash) ?? null; -} - -// domains/comet-native/native-creation-authorization.ts -var NATIVE_CREATION_AUTHORIZATION_SCHEMA = "comet.native.creation-authorization.v1"; -var HASH_PATTERN3 = /^[a-f0-9]{64}$/u; -var CHANGE_NAME_PATTERN2 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; -function record3(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); - } - return value; -} -function exactKeys3(value, expected, label) { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - if (JSON.stringify(actual) !== JSON.stringify(canonical)) { - throw new Error(`${label} fields are invalid`); - } -} -function parseNativeCreationAuthorization(value) { - const root = record3(value, "Native creation authorization"); - exactKeys3( - root, - [ - "schema", - "controllerKeyId", - "projectRootHash", - "policyHash", - "protocol", - "change", - "issuedAt", - "authorizationHash", - "controllerSignature" - ], - "Native creation authorization" - ); - if (root.schema !== NATIVE_CREATION_AUTHORIZATION_SCHEMA || typeof root.controllerKeyId !== "string" || !HASH_PATTERN3.test(root.controllerKeyId) || typeof root.projectRootHash !== "string" || !HASH_PATTERN3.test(root.projectRootHash) || typeof root.policyHash !== "string" || !HASH_PATTERN3.test(root.policyHash) || root.protocol !== "signed-v2" || typeof root.change !== "string" || !CHANGE_NAME_PATTERN2.test(root.change) || typeof root.issuedAt !== "string" || Number.isNaN(Date.parse(root.issuedAt)) || typeof root.authorizationHash !== "string" || !HASH_PATTERN3.test(root.authorizationHash)) { - throw new Error("Native creation authorization is invalid"); - } - const content = { - schema: NATIVE_CREATION_AUTHORIZATION_SCHEMA, - controllerKeyId: root.controllerKeyId, - projectRootHash: root.projectRootHash, - policyHash: root.policyHash, - protocol: "signed-v2", - change: root.change, - issuedAt: root.issuedAt - }; - const authorizationHash = canonicalHash(NATIVE_CREATION_AUTHORIZATION_SCHEMA, content); - if (authorizationHash !== root.authorizationHash) { - throw new Error("Native creation authorization hash mismatch"); - } - const controllerSignature = parseNativeReviewSignature(root.controllerSignature); - if (controllerSignature.keyId !== root.controllerKeyId || controllerSignature.payloadHash !== authorizationHash) { - throw new Error("Native creation authorization signature binding is invalid"); - } - return { ...content, authorizationHash, controllerSignature }; -} -async function verifyNativeCreationAuthorization(options) { - const controllerTrust = await readNativeControllerTrustProject(options.paths.projectRoot); - if (!controllerTrust) { - throw new Error("Native project has no controller-owned trust root"); - } - const authorization = parseNativeCreationAuthorization(options.authorization); - const projectRootHash = await nativeControllerProjectRootHash(options.paths.projectRoot); - if (authorization.controllerKeyId !== controllerTrust.controllerIdentity.keyId || authorization.projectRootHash !== projectRootHash || authorization.policyHash !== options.policyHash || authorization.change !== options.change) { - throw new Error("Native creation authorization does not match the trusted project/change"); - } - verifyNativeReviewPayloadHash({ - identity: controllerTrust.controllerIdentity, - payloadHash: authorization.authorizationHash, - proof: authorization.controllerSignature - }); - return authorization; -} - // domains/comet-native/native-config.ts init_project_config(); init_project_config_reader(); @@ -10994,13 +10471,13 @@ var nativeLockCoordinator = new AsyncLocalStorage(); import { TextDecoder as TextDecoder4 } from "util"; // domains/comet-native/native-protected-file.ts -import { createHash as createHash5 } from "node:crypto"; -import { constants as fsConstants3, promises as fs12 } from "node:fs"; -import path16 from "node:path"; +import { createHash as createHash3 } from "node:crypto"; +import { constants as fsConstants2, promises as fs10 } from "node:fs"; +import path14 from "node:path"; import { TextDecoder as TextDecoder3 } from "node:util"; -function isInside5(parent, target) { - const relative = path16.relative(parent, target); - return relative === "" || !path16.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path16.sep}`); +function isInside4(parent, target) { + const relative = path14.relative(parent, target); + return relative === "" || !path14.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path14.sep}`); } function positiveLimit2(value) { if (!Number.isSafeInteger(value) || value < 1) { @@ -11037,31 +10514,31 @@ function sameFileIdentity3(expected, actual) { ) && expected.birthtimeMs === actual.birthtimeMs && expected.ctimeMs === actual.ctimeMs && expected.mtimeMs === actual.mtimeMs && expected.size === actual.size; } async function captureDirectoryIdentity2(directory, label) { - const stat = await fs12.lstat(directory); + const stat = await fs10.lstat(directory); if (!stat.isDirectory() || stat.isSymbolicLink()) { throw new Error(`${label} parent must be a real directory: ${directory}`); } return { path: directory, - realPath: await fs12.realpath(directory), + realPath: await fs10.realpath(directory), dev: stat.dev, ino: stat.ino, birthtimeMs: stat.birthtimeMs }; } async function captureDirectoryChain2(root, directory, label) { - const lexicalRoot = path16.resolve(root); - const lexicalDirectory = path16.resolve(directory); - if (!isInside5(lexicalRoot, lexicalDirectory)) { + const lexicalRoot = path14.resolve(root); + const lexicalDirectory = path14.resolve(directory); + if (!isInside4(lexicalRoot, lexicalDirectory)) { throw new Error(`${label} is outside its managed root`); } const chain = [await captureDirectoryIdentity2(lexicalRoot, label)]; let cursor = lexicalRoot; - for (const segment of path16.relative(lexicalRoot, lexicalDirectory).split(path16.sep).filter(Boolean)) { + for (const segment of path14.relative(lexicalRoot, lexicalDirectory).split(path14.sep).filter(Boolean)) { await verifyDirectoryChain3(chain, label); - cursor = path16.join(cursor, segment); + cursor = path14.join(cursor, segment); const identity = await captureDirectoryIdentity2(cursor, label); - if (!isInside5(chain[0].realPath, identity.realPath)) { + if (!isInside4(chain[0].realPath, identity.realPath)) { throw new Error(`${label} parent resolves outside its managed root: ${cursor}`); } chain.push(identity); @@ -11071,8 +10548,8 @@ async function captureDirectoryChain2(root, directory, label) { } async function verifyDirectoryChain3(chain, label) { for (const identity of chain) { - const stat = await fs12.lstat(identity.path); - if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await fs12.realpath(identity.path) !== identity.realPath) { + const stat = await fs10.lstat(identity.path); + if (!stat.isDirectory() || stat.isSymbolicLink() || !sameDirectoryIdentity3(identity, stat) || await fs10.realpath(identity.path) !== identity.realPath) { throw new Error(`${label} parent changed during I/O: ${identity.path}`); } } @@ -11093,36 +10570,36 @@ async function readHandleBounded(handle, maxBytes, label) { } async function readNativeProtectedFile(options) { const maxBytes = positiveLimit2(options.maxBytes); - const file = path16.resolve(options.file); - const chain = await captureDirectoryChain2(options.root, path16.dirname(file), options.label); + const file = path14.resolve(options.file); + const chain = await captureDirectoryChain2(options.root, path14.dirname(file), options.label); const forbidden = await Promise.all( (options.forbiddenRoots ?? []).map( - (root) => captureDirectoryIdentity2(path16.resolve(root), options.label) + (root) => captureDirectoryIdentity2(path14.resolve(root), options.label) ) ); await options.hooks?.afterParentChainCaptured?.(); await verifyDirectoryChain3(chain, options.label); - const before = await fs12.lstat(file); + const before = await fs10.lstat(file); if (!before.isFile() || before.isSymbolicLink()) { throw new Error(`${options.label} must be a regular file`); } if (before.size > maxBytes) throw new Error(`${options.label} exceeds ${maxBytes} bytes`); const beforeIdentity = asFileIdentity(before); - const beforeRealPath = await fs12.realpath(file); - if (!isInside5(chain[0].realPath, beforeRealPath)) { + const beforeRealPath = await fs10.realpath(file); + if (!isInside4(chain[0].realPath, beforeRealPath)) { throw new Error(`${options.label} resolves outside its managed root`); } - if (forbidden.some((identity) => isInside5(identity.realPath, beforeRealPath))) { + if (forbidden.some((identity) => isInside4(identity.realPath, beforeRealPath))) { throw new Error(`${options.label} resolves inside an excluded root`); } - const flags = process.platform === "win32" ? fsConstants3.O_RDONLY : fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW | fsConstants3.O_NONBLOCK; - const handle = await fs12.open(file, flags); + const flags = process.platform === "win32" ? fsConstants2.O_RDONLY : fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW | fsConstants2.O_NONBLOCK; + const handle = await fs10.open(file, flags); try { const opened = await handle.stat(); await options.hooks?.afterOpen?.(); const [pathAfterOpen, realPathAfterOpen] = await Promise.all([ - fs12.lstat(file), - fs12.realpath(file) + fs10.lstat(file), + fs10.realpath(file) ]); await verifyDirectoryChain3(chain, options.label); await verifyDirectoryChain3(forbidden, options.label); @@ -11134,8 +10611,8 @@ async function readNativeProtectedFile(options) { await options.hooks?.beforeFinalCheck?.(); const [afterHandle, afterPath, afterRealPath] = await Promise.all([ handle.stat(), - fs12.lstat(file), - fs12.realpath(file) + fs10.lstat(file), + fs10.realpath(file) ]); await verifyDirectoryChain3(chain, options.label); await verifyDirectoryChain3(forbidden, options.label); @@ -11144,7 +10621,7 @@ async function readNativeProtectedFile(options) { } return { bytes, - hash: createHash5("sha256").update(bytes).digest("hex"), + hash: createHash3("sha256").update(bytes).digest("hex"), size: bytes.length }; } finally { @@ -11169,104 +10646,13 @@ var NATIVE_TRANSACTION_EVENT_MAX_BYTES = 16 * 1024; var NATIVE_LEGACY_TRANSACTION_FILE_MAX_BYTES = 64 * 1024 * 1024; var UTF8_DECODER = new TextDecoder4("utf-8", { fatal: true }); -// domains/comet-native/native-review-trust.ts -var NATIVE_REVIEW_TRUST_POLICY_SCHEMA = "comet.native.review-trust-policy.v2"; -var POLICY_HASH_TAG = NATIVE_REVIEW_TRUST_POLICY_SCHEMA; -var MAX_POLICY_BYTES = 64 * 1024; -var HASH_PATTERN4 = /^[a-f0-9]{64}$/u; -function record4(value, label) { - if (!value || typeof value !== "object" || Array.isArray(value)) { - throw new Error(`${label} must be an object`); - } - return value; -} -function exactKeys4(value, expected, label) { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - if (JSON.stringify(actual) !== JSON.stringify(canonical)) { - throw new Error(`${label} fields are invalid`); - } -} -function identities(value, label) { - if (!Array.isArray(value) || value.length === 0 || value.length > 64) { - throw new Error(`${label} must be a bounded non-empty array`); - } - const parsed = value.map(parseNativeReviewIdentity).sort((left, right) => left.keyId.localeCompare(right.keyId, "en")); - if (new Set(parsed.map((identity) => identity.keyId)).size !== parsed.length || JSON.stringify(value) !== JSON.stringify(parsed)) { - throw new Error(`${label} must be sorted and unique`); - } - return parsed; -} -function parseNativeReviewTrustPolicy(value) { - const root = record4(value, "Native review trust policy"); - exactKeys4( - root, - [ - "schema", - "controllerKeyId", - "implementationKeyId", - "trustedReviewers", - "trustedWaiverSigners", - "policyHash", - "controllerSignature" - ], - "Native review trust policy" - ); - if (root.schema !== NATIVE_REVIEW_TRUST_POLICY_SCHEMA || typeof root.controllerKeyId !== "string" || !HASH_PATTERN4.test(root.controllerKeyId) || typeof root.implementationKeyId !== "string" || !HASH_PATTERN4.test(root.implementationKeyId) || typeof root.policyHash !== "string" || !HASH_PATTERN4.test(root.policyHash)) { - throw new Error("Native review trust policy identity or hash is invalid"); - } - const trustedReviewers = identities(root.trustedReviewers, "Native trusted reviewers"); - const trustedWaiverSigners = identities( - root.trustedWaiverSigners, - "Native trusted waiver signers" - ); - if (trustedReviewers.some((identity) => identity.keyId === root.implementationKeyId) || trustedWaiverSigners.some((identity) => identity.keyId === root.implementationKeyId) || (/* @__PURE__ */ new Set([ - root.controllerKeyId, - root.implementationKeyId, - ...trustedReviewers.map((identity) => identity.keyId), - ...trustedWaiverSigners.map((identity) => identity.keyId) - ])).size !== 2 + trustedReviewers.length + trustedWaiverSigners.length) { - throw new Error( - "Native controller, implementation, reviewer, and waiver signer identities must be globally distinct" - ); - } - const content = { - schema: NATIVE_REVIEW_TRUST_POLICY_SCHEMA, - controllerKeyId: root.controllerKeyId, - implementationKeyId: root.implementationKeyId, - trustedReviewers, - trustedWaiverSigners - }; - const policyHash = canonicalHash(POLICY_HASH_TAG, content); - if (policyHash !== root.policyHash) { - throw new Error("Native review trust policy hash mismatch"); - } - const controllerSignature = parseNativeReviewSignature(root.controllerSignature); - if (controllerSignature.keyId !== root.controllerKeyId || controllerSignature.payloadHash !== policyHash) { - throw new Error("Native review trust policy controller signature binding is invalid"); - } - return { ...content, policyHash, controllerSignature }; -} -function verifyNativeReviewTrustPolicy(value, controllerIdentity) { - const policy = parseNativeReviewTrustPolicy(value); - if (policy.controllerKeyId !== controllerIdentity.keyId) { - throw new Error("Native review trust policy controller is not host-trusted"); - } - verifyNativeReviewPayloadHash({ - identity: controllerIdentity, - payloadHash: policy.policyHash, - proof: policy.controllerSignature - }); - return policy; -} - // domains/comet-native/native-snapshot.ts -import path17 from "path"; +import path15 from "path"; // domains/comet-native/native-hash.ts -import { createHash as createHash6 } from "crypto"; +import { createHash as createHash4 } from "crypto"; function sha256Text(content) { - return createHash6("sha256").update(content).digest("hex"); + return createHash4("sha256").update(content).digest("hex"); } // domains/comet-native/native-snapshot.ts @@ -11278,11 +10664,10 @@ var DEFAULT_NATIVE_SNAPSHOT_LIMITS = { }; var MAX_RECORDED_OMISSIONS = 1e3; var NATIVE_SNAPSHOT_MANIFEST_HARD_MAX_BYTES = 8 * 1024 * 1024; -var CHANGE_NAME_PATTERN3 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; +var CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var MANIFEST_KEYS = /* @__PURE__ */ new Set([ "schema", "origin", - "creation", "capture", "createdAt", "complete", @@ -11302,14 +10687,6 @@ var LIMIT_KEYS = /* @__PURE__ */ new Set([ ]); var POLICY_KEYS = /* @__PURE__ */ new Set(["schema", "include", "exclude", "hash"]); var CAPTURE_KEYS = /* @__PURE__ */ new Set(["provider", "gitSelection", "physicalSelection", "projection"]); -var CREATION_KEYS = /* @__PURE__ */ new Set([ - "schema", - "protocol", - "policyHash", - "policySnapshotRef", - "policySnapshotHash", - "authorization" -]); var GIT_PROJECTION_KEYS = /* @__PURE__ */ new Set(["provider", "selection"]); var GIT_SELECTION_KEYS = /* @__PURE__ */ new Set([ "schema", @@ -11363,7 +10740,7 @@ var OMISSION_REASONS = /* @__PURE__ */ new Set([ "physical-enumeration-limit", "physical-selection-changed" ]); -var HASH_PATTERN5 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN = /^[a-f0-9]{64}$/u; var GIT_LIST_STDERR_LIMIT = 64 * 1024; var GIT_TEXT_STDOUT_LIMIT = 64 * 1024; var DEFAULT_NATIVE_GIT_SELECTION_LIMITS = { @@ -11495,7 +10872,7 @@ function resolveSnapshotPolicy(value) { excludeMatchers: exclude.map(compileNativeSnapshotPattern) }; } -function record5(value, label) { +function record(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be an object`); } @@ -11521,17 +10898,17 @@ function snapshotPath(value, label) { if (typeof value !== "string" || value.length === 0 || value.includes("\\") || value.includes("\0")) { throw new Error(`${label} must be a normalized project-relative path`); } - const normalized2 = path17.posix.normalize(value); - if (normalized2 !== value || path17.posix.isAbsolute(value) || normalized2 === ".." || normalized2.startsWith("../")) { + const normalized2 = path15.posix.normalize(value); + if (normalized2 !== value || path15.posix.isAbsolute(value) || normalized2 === ".." || normalized2.startsWith("../")) { throw new Error(`${label} must stay inside the project root`); } return value; } function parseEntry(value, index) { - const entry = record5(value, `Native snapshot entry ${index}`); + const entry = record(value, `Native snapshot entry ${index}`); rejectUnknown(entry, ENTRY_KEYS, `Native snapshot entry ${index}`); const entryPath = snapshotPath(entry.path, `Native snapshot entry ${index} path`); - if (typeof entry.hash !== "string" || !HASH_PATTERN5.test(entry.hash)) { + if (typeof entry.hash !== "string" || !HASH_PATTERN.test(entry.hash)) { throw new Error(`Native snapshot entry ${index} hash is invalid`); } if (entry.type !== "file") throw new Error(`Native snapshot entry ${index} type is invalid`); @@ -11543,7 +10920,7 @@ function parseEntry(value, index) { }; } function parseOmission(value, index) { - const omission = record5(value, `Native snapshot omission ${index}`); + const omission = record(value, `Native snapshot omission ${index}`); rejectUnknown(omission, OMISSION_KEYS, `Native snapshot omission ${index}`); if (!OMISSION_TYPES.has(omission.type)) { throw new Error(`Native snapshot omission ${index} type is invalid`); @@ -11559,9 +10936,9 @@ function parseOmission(value, index) { }; } function parseOmissionOverflow(value) { - const overflow = record5(value, "Native snapshot omission overflow"); + const overflow = record(value, "Native snapshot omission overflow"); rejectUnknown(overflow, OMISSION_OVERFLOW_KEYS, "Native snapshot omission overflow"); - if (typeof overflow.hash !== "string" || !HASH_PATTERN5.test(overflow.hash)) { + if (typeof overflow.hash !== "string" || !HASH_PATTERN.test(overflow.hash)) { throw new Error("Native snapshot omission overflow hash is invalid"); } const expectedRef = `native-snapshot://omitted-overflow/${overflow.hash}`; @@ -11575,9 +10952,9 @@ function parseOmissionOverflow(value) { }; } function parseGitSelectionStreamEvidence(value, label) { - const stream = record5(value, label); + const stream = record(value, label); rejectUnknown(stream, GIT_SELECTION_STREAM_KEYS, label); - if (typeof stream.hash !== "string" || !HASH_PATTERN5.test(stream.hash)) { + if (typeof stream.hash !== "string" || !HASH_PATTERN.test(stream.hash)) { throw new Error(`${label} hash is invalid`); } if (typeof stream.overflow !== "boolean") { @@ -11601,7 +10978,7 @@ function parseGitSelectionStreamEvidence(value, label) { }; } function parseGitSelectionEvidence(value) { - const selection = record5(value, "Native Git selection evidence"); + const selection = record(value, "Native Git selection evidence"); rejectUnknown(selection, GIT_SELECTION_KEYS, "Native Git selection evidence"); if (selection.schema !== "comet.native.git-selection.v1") { throw new Error("Native Git selection evidence schema is invalid"); @@ -11661,9 +11038,9 @@ function parseGitSelectionEvidence(value) { }; } function parsePhysicalSelectionStreamEvidence(value, label) { - const stream = record5(value, label); + const stream = record(value, label); rejectUnknown(stream, PHYSICAL_SELECTION_STREAM_KEYS, label); - if (typeof stream.hash !== "string" || !HASH_PATTERN5.test(stream.hash)) { + if (typeof stream.hash !== "string" || !HASH_PATTERN.test(stream.hash)) { throw new Error(`${label} hash is invalid`); } if (typeof stream.overflow !== "boolean" || typeof stream.unstable !== "boolean") { @@ -11690,7 +11067,7 @@ function parsePhysicalSelectionStreamEvidence(value, label) { }; } function parsePhysicalSelectionEvidence(value) { - const selection = record5(value, "Native physical selection evidence"); + const selection = record(value, "Native physical selection evidence"); rejectUnknown(selection, PHYSICAL_SELECTION_KEYS, "Native physical selection evidence"); if (selection.schema !== "comet.native.physical-selection.v1") { throw new Error("Native physical selection evidence schema is invalid"); @@ -11723,7 +11100,7 @@ function parsePhysicalSelectionEvidence(value) { }; } function parseNativeContentSnapshotManifest(value) { - const manifest = record5(value, "Native content snapshot manifest"); + const manifest = record(value, "Native content snapshot manifest"); rejectUnknown(manifest, MANIFEST_KEYS, "Native content snapshot manifest"); if (manifest.schema !== "comet.native.content-snapshot.v1") { throw new Error("Unsupported Native content snapshot schema"); @@ -11731,28 +11108,9 @@ function parseNativeContentSnapshotManifest(value) { if (!SNAPSHOT_ORIGINS.has(manifest.origin)) { throw new Error("Native content snapshot origin is invalid"); } - let creation; - if (manifest.creation !== void 0) { - const value2 = record5(manifest.creation, "Native change creation binding"); - rejectUnknown(value2, CREATION_KEYS, "Native change creation binding"); - if (value2.schema !== "comet.native.change-creation-binding.v1" || value2.protocol !== "signed-v2" || typeof value2.policyHash !== "string" || !HASH_PATTERN5.test(value2.policyHash) || typeof value2.policySnapshotHash !== "string" || !HASH_PATTERN5.test(value2.policySnapshotHash) || typeof value2.policySnapshotRef !== "string" || !/^runtime\/trust\/review-policy-[a-f0-9]{64}\.json$/u.test(value2.policySnapshotRef)) { - throw new Error("Native change creation binding is invalid"); - } - creation = { - schema: "comet.native.change-creation-binding.v1", - protocol: "signed-v2", - policyHash: value2.policyHash, - policySnapshotRef: value2.policySnapshotRef, - policySnapshotHash: value2.policySnapshotHash, - authorization: parseNativeCreationAuthorization(value2.authorization) - }; - } - if (manifest.origin !== "change-created" && creation !== void 0) { - throw new Error("Native change creation binding does not match snapshot origin"); - } let capture; if (manifest.capture !== void 0) { - const captureValue = record5(manifest.capture, "Native content snapshot capture"); + const captureValue = record(manifest.capture, "Native content snapshot capture"); rejectUnknown(captureValue, CAPTURE_KEYS, "Native content snapshot capture"); if (captureValue.provider !== "git" && captureValue.provider !== "physical-tree") { throw new Error("Native content snapshot capture provider is invalid"); @@ -11761,7 +11119,7 @@ function parseNativeContentSnapshotManifest(value) { const physicalSelection2 = captureValue.physicalSelection === void 0 ? void 0 : parsePhysicalSelectionEvidence(captureValue.physicalSelection); let projection = null; if (captureValue.projection !== void 0) { - const projectionValue = record5(captureValue.projection, "Native content snapshot projection"); + const projectionValue = record(captureValue.projection, "Native content snapshot projection"); rejectUnknown(projectionValue, GIT_PROJECTION_KEYS, "Native content snapshot projection"); if (projectionValue.provider !== "git") { throw new Error("Native content snapshot projection provider is invalid"); @@ -11798,7 +11156,7 @@ function parseNativeContentSnapshotManifest(value) { if (typeof manifest.complete !== "boolean") { throw new Error("Native content snapshot complete flag is invalid"); } - const limitValue = record5(manifest.limits, "Native content snapshot limits"); + const limitValue = record(manifest.limits, "Native content snapshot limits"); rejectUnknown(limitValue, LIMIT_KEYS, "Native content snapshot limits"); const limits = { maxFiles: positiveInteger(limitValue.maxFiles, "Native snapshot maxFiles"), @@ -11814,7 +11172,7 @@ function parseNativeContentSnapshotManifest(value) { }; let policy; if (manifest.policy !== void 0) { - const policyValue = record5(manifest.policy, "Native snapshot policy"); + const policyValue = record(manifest.policy, "Native snapshot policy"); rejectUnknown(policyValue, POLICY_KEYS, "Native snapshot policy"); if (policyValue.schema !== "comet.native.snapshot-policy.v1") { throw new Error("Native snapshot policy schema is invalid"); @@ -11907,7 +11265,6 @@ function parseNativeContentSnapshotManifest(value) { const parsed = { schema: "comet.native.content-snapshot.v1", origin: manifest.origin, - ...creation ? { creation } : {}, ...capture ? { capture } : {}, createdAt: manifest.createdAt, complete: manifest.complete, @@ -11924,10 +11281,10 @@ function parseNativeContentSnapshotManifest(value) { return parsed; } function nativeBaselineManifestFile(paths, name) { - if (!CHANGE_NAME_PATTERN3.test(name)) throw new Error(`Invalid Native change name: ${name}`); - const changeDir = path17.join(paths.changesDir, name); + if (!CHANGE_NAME_PATTERN.test(name)) throw new Error(`Invalid Native change name: ${name}`); + const changeDir = path15.join(paths.changesDir, name); if (!isInsidePath(paths.changesDir, changeDir)) throw new Error("Native change path escaped"); - return path17.join(changeDir, "runtime", "baseline-manifest.json"); + return path15.join(changeDir, "runtime", "baseline-manifest.json"); } async function readNativeBaselineManifest(paths, name) { const file = nativeBaselineManifestFile(paths, name); @@ -12000,7 +11357,7 @@ var PHASES2 = /* @__PURE__ */ new Set(["shape", "build", "verify", "archive"]); var APPROVALS = /* @__PURE__ */ new Set(["implicit", "confirmed"]); var VERIFY_RESULTS2 = /* @__PURE__ */ new Set(["pending", "pass", "fail"]); var NATIVE_CHANGE_STATE_FILE = "comet-state.yaml"; -var HASH_PATTERN6 = /^[a-f0-9]{64}$/u; +var HASH_PATTERN2 = /^[a-f0-9]{64}$/u; var NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; var CONTENT_ADDRESSED_REF_PATTERN = /^runtime\/evidence\/(scopes|allowances|verifications)\/([a-f0-9]{64})\.json$/u; var NativeSchemaMigrationRequiredError = class extends Error { @@ -12047,7 +11404,7 @@ var NATIVE_BRIEF_TEMPLATE = [ "# Verification expectations", "" ].join("\n"); -function record6(value, label) { +function record2(value, label) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error(`${label} must be a mapping`); } @@ -12064,12 +11421,12 @@ function assertCapabilityId(value) { if (!NAME_PATTERN.test(value)) throw new Error(`Invalid Native capability id: ${value}`); } function assertRelativeRef(value, label) { - if (value.length === 0 || path18.isAbsolute(value) || /^(?:[A-Za-z]:|~|[\\/])/u.test(value) || value.split(/[\\/]/u).includes("..")) { + if (value.length === 0 || path16.isAbsolute(value) || /^(?:[A-Za-z]:|~|[\\/])/u.test(value) || value.split(/[\\/]/u).includes("..")) { throw new Error(`${label} must stay inside the Native change`); } } function parseSpecChange(value, index) { - const item = record6(value, `spec_changes[${index}]`); + const item = record2(value, `spec_changes[${index}]`); rejectUnknown2(item, SPEC_CHANGE_KEYS, `spec_changes[${index}]`); if (typeof item.capability !== "string") throw new Error("spec change capability is required"); assertCapabilityId(item.capability); @@ -12088,12 +11445,12 @@ function parseSpecChange(value, index) { throw new Error(`Create spec ${item.capability} requires null base_hash`); } else if (item.operation === "replace") { if (!source) throw new Error(`Replace spec ${item.capability} requires source`); - if (typeof baseHash !== "string" || !HASH_PATTERN6.test(baseHash)) { + if (typeof baseHash !== "string" || !HASH_PATTERN2.test(baseHash)) { throw new Error(`Replace spec ${item.capability} requires a SHA-256 base_hash`); } } else { if (source !== void 0) throw new Error(`Remove spec ${item.capability} forbids source`); - if (typeof baseHash !== "string" || !HASH_PATTERN6.test(baseHash)) { + if (typeof baseHash !== "string" || !HASH_PATTERN2.test(baseHash)) { throw new Error(`Remove spec ${item.capability} requires a SHA-256 base_hash`); } } @@ -12161,7 +11518,7 @@ function parseChangeFields(root, knownKeys) { }; } function parseLegacyNativeChangeValue(value) { - const root = record6(value, NATIVE_CHANGE_STATE_FILE); + const root = record2(value, NATIVE_CHANGE_STATE_FILE); if (root.schema !== NATIVE_LEGACY_CHANGE_SCHEMA) { throw new Error(`Expected ${NATIVE_LEGACY_CHANGE_SCHEMA}`); } @@ -12188,20 +11545,20 @@ function contentAddressedRef(value, label, kind) { } function approvedContractHash(value) { if (value === void 0 || value === null) return null; - if (typeof value !== "string" || !HASH_PATTERN6.test(value)) { + if (typeof value !== "string" || !HASH_PATTERN2.test(value)) { throw new Error("Native approved_contract_hash must be null or a SHA-256 hash"); } return value; } function verificationProtocol(value) { if (value === void 0) return "legacy-v1"; - if (value !== "legacy-v1" && value !== "signed-v2") { - throw new Error("Native verification_protocol must be legacy-v1 or signed-v2"); + if (value !== "legacy-v1") { + throw new Error("Native verification_protocol must be legacy-v1"); } return value; } function parseV2NativeChangeValue(value) { - const root = record6(value, NATIVE_CHANGE_STATE_FILE); + const root = record2(value, NATIVE_CHANGE_STATE_FILE); if (root.schema !== NATIVE_V2_CHANGE_SCHEMA) { throw new Error(`Expected ${NATIVE_V2_CHANGE_SCHEMA}`); } @@ -12220,7 +11577,7 @@ function parseV2NativeChangeValue(value) { }; } function parseNativeChangeValue(value) { - const root = record6(value, NATIVE_CHANGE_STATE_FILE); + const root = record2(value, NATIVE_CHANGE_STATE_FILE); if (root.schema !== NATIVE_CHANGE_SCHEMA) { if (root.schema === NATIVE_LEGACY_CHANGE_SCHEMA || root.schema === NATIVE_V2_CHANGE_SCHEMA) { const previous = root.schema === NATIVE_LEGACY_CHANGE_SCHEMA ? parseLegacyNativeChangeValue(root) : parseV2NativeChangeValue(root); @@ -12274,7 +11631,7 @@ function parseNativeChangeValue(value) { }; } function inspectNativeChangeValue(value) { - const root = record6(value, NATIVE_CHANGE_STATE_FILE); + const root = record2(value, NATIVE_CHANGE_STATE_FILE); if (root.schema === NATIVE_LEGACY_CHANGE_SCHEMA) { const state2 = parseLegacyNativeChangeValue(root); return { @@ -12331,15 +11688,15 @@ function inspectNativeChangeValue(value) { } function nativeChangeDir(paths, name) { assertNativeName(name); - const target = path18.join(paths.changesDir, name); + const target = path16.join(paths.changesDir, name); if (!isInsidePath(paths.changesDir, target)) throw new Error("Native change path escaped"); return target; } async function hasPendingNativeSchemaMigration(paths, name) { - const file = path18.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json"); + const file = path16.join(nativeChangeDir(paths, name), "runtime", "schema-migration.json"); await resolveContainedNativePath(paths.nativeRoot, file); try { - await fs13.lstat(file); + await fs11.lstat(file); return true; } catch (error) { if (error.code === "ENOENT") return false; @@ -12347,8 +11704,8 @@ async function hasPendingNativeSchemaMigration(paths, name) { } } var NATIVE_CHANGE_DOCUMENT_MAX_BYTES = 256 * 1024; -async function readChangeDocumentFile(file, root = path18.dirname(file)) { - const ref = path18.relative(root, file).split(path18.sep).join("/"); +async function readChangeDocumentFile(file, root = path16.dirname(file)) { + const ref = path16.relative(root, file).split(path16.sep).join("/"); const source = await readNativeBoundedTextFile({ root, ref, @@ -12361,7 +11718,7 @@ async function readChangeDocumentFile(file, root = path18.dirname(file)) { return document.toJS(); } async function inspectNativeChange(paths, name) { - const file = path18.join(nativeChangeDir(paths, name), NATIVE_CHANGE_STATE_FILE); + const file = path16.join(nativeChangeDir(paths, name), NATIVE_CHANGE_STATE_FILE); await resolveContainedNativePath(paths.nativeRoot, file); const inspection = inspectNativeChangeValue(await readChangeDocumentFile(file, paths.nativeRoot)); if (inspection.state && inspection.state.name !== name) { @@ -12382,67 +11739,10 @@ async function inspectNativeChange(paths, name) { return inspection; } async function assertNativeVerificationProtocolBinding(paths, state) { - const controllerTrust = await readNativeControllerTrustProject(paths.projectRoot); - const controllerRequiresSigned = controllerTrust !== null && !controllerTrust.legacyChanges.includes(state.name); - if (controllerRequiresSigned && state.verification_protocol !== "signed-v2") { - throw new Error( - `Native verification protocol does not match its creation baseline (controller-backed): expected signed-v2 for ${state.name}` - ); - } - if (controllerTrust !== null && controllerTrust.legacyChanges.includes(state.name) && state.verification_protocol !== "legacy-v1") { - throw new Error(`Native controller trust marks ${state.name} as legacy-v1`); - } const baseline = await readNativeBaselineManifest(paths, state.name); - if (baseline === null) { - if (state.verification_protocol === "signed-v2" || controllerRequiresSigned) { - throw new Error( - `Native signed-v2 verification protocol has no creation baseline: ${state.name}` - ); - } - return; - } - const expectedProtocol = baseline.creation ? "signed-v2" : "legacy-v1"; - if (state.verification_protocol !== expectedProtocol) { - throw new Error( - `Native verification protocol does not match its creation baseline: expected ${expectedProtocol}, got ${state.verification_protocol}` - ); - } - if (expectedProtocol === "signed-v2") { - if (!controllerTrust) { - throw new Error("Native signed-v2 creation has no controller-owned trust root"); - } - const creation = baseline.creation; - const changeDir = nativeChangeDir(paths, state.name); - const snapshot = await readNativeProtectedTextFile({ - root: nativeChangeDir(paths, state.name), - file: path18.join(changeDir, ...creation.policySnapshotRef.split("/")), - maxBytes: 64 * 1024, - label: "Native creation-time trust policy snapshot" - }); - if (createHash7("sha256").update(snapshot.text).digest("hex") !== creation.policySnapshotHash) { - throw new Error("Native creation-time trust policy snapshot hash mismatch"); - } - let policy; - try { - policy = JSON.parse(snapshot.text); - } catch (error) { - throw new Error("Native creation-time trust policy snapshot is invalid JSON", { - cause: error - }); - } - const verifiedPolicy = verifyNativeReviewTrustPolicy( - policy, - controllerTrust.controllerIdentity - ); - if (verifiedPolicy.policyHash !== creation.policyHash) { - throw new Error("Native creation-time trust policy snapshot does not match its binding"); - } - await verifyNativeCreationAuthorization({ - paths, - policyHash: creation.policyHash, - authorization: creation.authorization, - change: state.name - }); + if (baseline === null) return; + if (state.verification_protocol !== "legacy-v1") { + throw new Error(`Native verification protocol is unsupported: ${state.verification_protocol}`); } } async function readNativeChange(paths, name) { @@ -12473,14 +11773,14 @@ async function resolveSelectedNativeChange(paths) { // domains/comet-native/native-hook-guard.ts function isWithin(parent, target) { - const relative = path19.relative(parent, target); - return relative === "" || !relative.startsWith("..") && !path19.isAbsolute(relative); + const relative = path17.relative(parent, target); + return relative === "" || !relative.startsWith("..") && !path17.isAbsolute(relative); } function requestTargetsAreControlOnly(projectRoot, nativeRoot, request) { return request.targets.length > 0 && request.targets.every((targetPath) => { - const target = path19.resolve(projectRoot, targetPath); + const target = path17.resolve(projectRoot, targetPath); if (!isWithin(projectRoot, target)) return true; - const relative = path19.relative(projectRoot, target).replaceAll("\\", "/"); + const relative = path17.relative(projectRoot, target).replaceAll("\\", "/"); return relative === ".comet/config.yaml" || isWithin(nativeRoot, target); }); } @@ -12490,7 +11790,7 @@ async function activeNativeContext(projectRoot) { const paths = await nativeProjectPaths(projectRoot, config.native.artifact_root); let entries; try { - entries = await fs14.readdir(paths.changesDir, { withFileTypes: true }); + entries = await fs12.readdir(paths.changesDir, { withFileTypes: true }); } catch (error) { if (error.code === "ENOENT") return { paths, changes: [] }; throw error; @@ -12568,12 +11868,12 @@ async function inspectNativeHookGuard(projectRoot, request, selectedChangeName) let controlTarget = false; let externalTarget = false; for (const targetPath of request.targets) { - const target = path19.resolve(projectRoot, targetPath); + const target = path17.resolve(projectRoot, targetPath); if (!isWithin(projectRoot, target)) { externalTarget = true; continue; } - const relative = path19.relative(projectRoot, target).replaceAll("\\", "/"); + const relative = path17.relative(projectRoot, target).replaceAll("\\", "/"); if (relative === ".comet/config.yaml" || isWithin(context.paths.nativeRoot, target)) { controlTarget = true; continue; @@ -12731,14 +12031,14 @@ function parseArgs(args) { throw new Error(`unsupported Hook platform: ${platformId}`); } if (projectRoot?.startsWith("--")) throw new Error("--project-root requires a value"); - return { platformId, ...projectRoot ? { projectRoot: path20.resolve(projectRoot) } : {} }; + return { platformId, ...projectRoot ? { projectRoot: path18.resolve(projectRoot) } : {} }; } async function projectRootFrom(parsed) { if (parsed.projectRoot) return parsed.projectRoot; const discovered = await discoverNativeProject(process.cwd()); for (const marker of [[".comet", "config.yaml"], [".git"]]) { try { - await fs15.lstat(path20.join(discovered, ...marker)); + await fs13.lstat(path18.join(discovered, ...marker)); return discovered; } catch (error) { if (error.code !== "ENOENT") throw error; @@ -12747,7 +12047,7 @@ async function projectRootFrom(parsed) { const classic = await discoverClassicProject(process.cwd()); const layout = await assertClassicLayoutReadable(classic); try { - await fs15.lstat(layout.changesDir); + await fs13.lstat(layout.changesDir); return classic; } catch (error) { if (error.code !== "ENOENT") throw error; diff --git a/assets/skills/comet/scripts/comet-runtime.mjs b/assets/skills/comet/scripts/comet-runtime.mjs index c9db695c..cda2f2f2 100644 --- a/assets/skills/comet/scripts/comet-runtime.mjs +++ b/assets/skills/comet/scripts/comet-runtime.mjs @@ -7442,7 +7442,7 @@ function normalizeWorkflowArtifactRoot(value) { const segments = projectRelativeSegments(value, "native.artifact_root"); return segments.length === 0 ? "." : segments.join("/"); } -function normalizeClassicArtifactLayout(value, fallback = "legacy") { +function normalizeClassicArtifactLayout(value, fallback = "docs") { const resolved = value ?? fallback; if (resolved !== "legacy" && resolved !== "docs") { throw new Error("classic.artifact_layout must be legacy or docs"); @@ -7745,7 +7745,7 @@ var init_project_config = __esm({ "native.snapshot.max_total_bytes": "# Bounds the total file content hashed by one snapshot. Content is streamed and does not depend on Git hashes.", "native.snapshot.max_duration_ms": "# Bounds snapshot capture time in milliseconds. Increase it together with the byte budget on slower or larger repositories.", classic: "# Classic workflow settings. They do not change Native state or behavior.", - "classic.artifact_layout": "# Selects the Classic artifact layout. New projects use docs; existing projects remain legacy until explicitly migrated.\n# artifact_layout: legacy | docs", + "classic.artifact_layout": "# Selects the Classic artifact layout. The default is docs; update preserves detected root-level legacy artifacts.\n# artifact_layout: legacy | docs", "classic.language": "# Artifact language used by Classic workflow documents.\n# language: en | zh-CN", "classic.context_compression": "# Controls beta context compression for new Classic changes.\n# context_compression: off | beta", "classic.review_mode": "# Sets the default review depth for new Classic changes.\n# review_mode: off | standard | thorough", @@ -7769,7 +7769,7 @@ var init_project_config = __esm({ "native.snapshot.max_total_bytes": "# 单次快照最多哈希的文件内容总字节数;内容采用流式读取,不依赖 Git hash。", "native.snapshot.max_duration_ms": "# 单次快照的最长执行时间(毫秒);较慢或更大的仓库应与字节预算一并提高。", classic: "# Classic 工作流配置,不会改变 Native 的状态或行为。", - "classic.artifact_layout": "# Classic 产物布局;新项目使用 docs,已有项目在显式迁移前保持 legacy。\n# 可选值:legacy | docs", + "classic.artifact_layout": "# Classic 产物布局;默认使用 docs,update 检测到根目录 legacy 产物时予以保留。\n# 可选值:legacy | docs", "classic.language": "# Classic 工作流文档使用的产物语言。\n# 可选值:en | zh-CN", "classic.context_compression": "# 新建 Classic change 是否启用 beta 上下文压缩。\n# 可选值:off | beta", "classic.review_mode": "# 新建 Classic change 默认使用的审查深度。\n# 可选值:off | standard | thorough", diff --git a/docs/comet/specs/native-completion-loop/spec.md b/docs/comet/specs/native-completion-loop/spec.md index 1e464069..71918ff7 100644 --- a/docs/comet/specs/native-completion-loop/spec.md +++ b/docs/comet/specs/native-completion-loop/spec.md @@ -42,7 +42,8 @@ Native 必须把一次 Agent turn 视为一次可恢复迭代,而不是完成 ## 兼容与验证 - 不新增 Native phase、独立 Loop Engine、新 CLI 命令、Goal 状态文件或外部 Skill 依赖。 -- 保持旧 archive 可读,并保持 #240 signed-v2 证据与独立审查边界。 +- 删除 change 创建授权和 `signed-v2` 创建协议,不提供兼容读取或可选 trust mode;高风险独立审查只由当前 implementation scope 触发。 +- 不含已删除创建授权格式的旧 archive 继续可读。 - acceptance page、status 和 continuation 输出必须有预算、可分页并跨平台稳定。 - 中英文 Native Skill、Runtime 源码和生成资产必须同步。 - 回归测试和真实生命周期 Eval 必须覆盖“遗漏 spec → 回 Build 修复 → 再 Verify → Archive”、语义停滞停止、总预算,以及两种归档配置。 diff --git a/docs/comet/specs/native-verification-evidence/spec.md b/docs/comet/specs/native-verification-evidence/spec.md index 6566976e..97358623 100644 --- a/docs/comet/specs/native-verification-evidence/spec.md +++ b/docs/comet/specs/native-verification-evidence/spec.md @@ -38,7 +38,7 @@ ## 漂移与兼容 - Verify 后 contract、implementation scope、当前工作树、verification report、receipt、waiver 或 review 任一变化,当前通过结论失效并回退到重新 Verify 的受控流程。 -- 新协议使用版本化 evidence schema。旧 archive、旧 envelope 和旧 report 继续支持 show/status/doctor 的只读解析,不要求回填 acceptance matrix 或 receipt。 +- 新协议使用版本化 evidence schema。不含已删除 `signed-v2` 创建授权格式的旧 archive、旧 envelope 和旧 report 继续支持 show/status/doctor 的只读解析,不要求回填 acceptance matrix 或 receipt;已删除的创建授权格式不提供兼容读取。 ## 验证要求 diff --git a/domains/comet-classic/classic-layout-initialization.ts b/domains/comet-classic/classic-layout-initialization.ts index ba98c6a8..517dc9f3 100644 --- a/domains/comet-classic/classic-layout-initialization.ts +++ b/domains/comet-classic/classic-layout-initialization.ts @@ -933,7 +933,7 @@ export async function repairClassicLayoutInitialization( const workflows = snapshot.document?.config?.workflows ?? (snapshot.document?.config ? [snapshot.document.config.default_workflow] : []); - const configuredLayout = snapshot.document?.classic?.artifact_layout ?? 'legacy'; + const configuredLayout = snapshot.document?.classic?.artifact_layout ?? journal.artifactLayout; if ( !workflows.includes('classic') || configuredLayout !== journal.artifactLayout || @@ -1086,7 +1086,7 @@ export async function assertClassicLayoutInitializationSafe( configIdentity, ownership.configIdentity, ); - const configuredLayout = config?.classic?.artifact_layout ?? 'legacy'; + const configuredLayout = config?.classic?.artifact_layout ?? desiredLayout; const committedConfigurationMatches = Boolean(config && classicEnabled && configuredLayout === desiredLayout) && ownership.rootIdentity !== null && @@ -1111,7 +1111,7 @@ export async function assertClassicLayoutInitializationSafe( } if (config && classicEnabled) { - const configuredLayout = config.classic?.artifact_layout ?? 'legacy'; + const configuredLayout = config.classic?.artifact_layout ?? desiredLayout; if (configuredLayout !== desiredLayout) { throw new Error( `Configured Classic layout is ${configuredLayout}, but OpenSpec initialization requested ${desiredLayout}`, diff --git a/domains/comet-entry/init-workflow.ts b/domains/comet-entry/init-workflow.ts index 7e1045a0..5d5dff32 100644 --- a/domains/comet-entry/init-workflow.ts +++ b/domains/comet-entry/init-workflow.ts @@ -126,12 +126,22 @@ export async function resolveInitWorkflow( const explicit = requestedWorkflow !== undefined || requestedArtifactRoot !== undefined; const configuredWorkflows = existing.workflows ?? [existing.default_workflow]; const classicAlreadyEnabled = configuredWorkflows.includes('classic'); + const rawClassic = snapshot.document?.value.classic; + const hasExplicitClassicLayout = + rawClassic !== null && + typeof rawClassic === 'object' && + !Array.isArray(rawClassic) && + (rawClassic as Record).artifact_layout !== undefined; + const inferredClassicLayout: ClassicArtifactLayout = hasExplicitClassicLayout + ? (existing.classic?.artifact_layout ?? 'docs') + : classicAlreadyEnabled && (await fileExists(path.join(projectRoot, 'openspec'))) + ? 'legacy' + : 'docs'; return { workflow, source: explicit ? 'explicit-option' : 'project-config', artifactRoot: requestedArtifactRoot ?? existing.native?.artifact_root ?? 'docs', - classicArtifactLayout: - existing.classic?.artifact_layout ?? (classicAlreadyEnabled ? 'legacy' : 'docs'), + classicArtifactLayout: inferredClassicLayout, writeProjectConfig: workflow !== existing.default_workflow || (workflow === 'native' && !existing.native), legacyEvidence: [], @@ -139,12 +149,17 @@ export async function resolveInitWorkflow( } const legacyEvidence = await findLegacyEvidence(projectRoot, snapshot.identity.exists); + const legacyArtifactLayout = + legacyEvidence.some((item) => item.startsWith('openspec/')) || + (snapshot.identity.exists && + (await fileExists(path.join(projectRoot, 'openspec'))) && + !(await fileExists(path.join(projectRoot, 'docs', 'openspec')))); if (requestedWorkflow) { return { workflow: requestedWorkflow, source: 'explicit-option', artifactRoot: requestedArtifactRoot ?? 'docs', - classicArtifactLayout: legacyEvidence.length > 0 ? 'legacy' : 'docs', + classicArtifactLayout: legacyArtifactLayout ? 'legacy' : 'docs', writeProjectConfig: true, legacyEvidence, }; @@ -154,7 +169,7 @@ export async function resolveInitWorkflow( workflow: 'classic', source: 'legacy-project', artifactRoot: 'docs', - classicArtifactLayout: 'legacy', + classicArtifactLayout: legacyArtifactLayout ? 'legacy' : 'docs', writeProjectConfig: false, legacyEvidence, }; diff --git a/domains/comet-native/native-change.ts b/domains/comet-native/native-change.ts index 696cc91d..ea181310 100644 --- a/domains/comet-native/native-change.ts +++ b/domains/comet-native/native-change.ts @@ -1,15 +1,8 @@ -import { createHash } from 'node:crypto'; import { promises as fs } from 'fs'; import path from 'path'; import { parseDocument, stringify } from 'yaml'; import { readNativeBoundedTextFile } from './native-bounded-file.js'; -import { - verifyNativeCreationAuthorization, - type NativeCreationAuthorization, -} from './native-creation-authorization.js'; -import { readNativeControllerTrustProject } from './native-controller-trust.js'; - import { atomicWriteText } from './native-atomic-file.js'; import { assertNoPendingNativeRootMove, @@ -18,14 +11,7 @@ import { } from './native-config.js'; import { withNativeMutationLock } from './native-mutation-lock.js'; import { isInsidePath, resolveContainedNativePath } from './native-paths.js'; -import { - readNativeProtectedDirectory, - readNativeProtectedTextFile, -} from './native-protected-file.js'; -import { - readNativeReviewTrustPolicy, - verifyNativeReviewTrustPolicy, -} from './native-review-trust.js'; +import { readNativeProtectedDirectory } from './native-protected-file.js'; import { compareAndSwapNativeRevision } from './native-revision.js'; import { createNativeContentSnapshot, @@ -355,8 +341,8 @@ function approvedContractHash(value: unknown): string | null { function verificationProtocol(value: unknown): NativeVerificationProtocol { if (value === undefined) return 'legacy-v1'; - if (value !== 'legacy-v1' && value !== 'signed-v2') { - throw new Error('Native verification_protocol must be legacy-v1 or signed-v2'); + if (value !== 'legacy-v1') { + throw new Error('Native verification_protocol must be legacy-v1'); } return value; } @@ -597,7 +583,6 @@ export async function createNativeChange(options: { name: string; language: 'en' | 'zh-CN'; verificationProtocol?: NativeVerificationProtocol; - creationAuthorization?: NativeCreationAuthorization; now?: Date; }): Promise { return withNativeMutationLock(options.paths, `create change ${options.name}`, () => @@ -610,41 +595,10 @@ async function createNativeChangeLocked(options: { name: string; language: 'en' | 'zh-CN'; verificationProtocol?: NativeVerificationProtocol; - creationAuthorization?: NativeCreationAuthorization; now?: Date; }): Promise { assertNativeName(options.name); - // The public CLI always passes signed-v2 explicitly. Keeping the low-level - // constructor's no-authorization default on legacy-v1 preserves internal - // migration/read fixtures without creating an unsigned signed-v2 change. - const verificationProtocol = - options.verificationProtocol ?? (options.creationAuthorization ? 'signed-v2' : 'legacy-v1'); - let signedCreation: { - policy: Awaited>; - authorization: NativeCreationAuthorization; - } | null = null; - if (verificationProtocol === 'signed-v2') { - const policy = await readNativeReviewTrustPolicy(options.paths).catch((error: unknown) => { - throw new Error( - 'New signed-v2 Native changes require a review trust policy before creation', - { cause: error }, - ); - }); - if (!options.creationAuthorization) { - throw new Error( - 'New signed-v2 Native changes require a controller-signed creation authorization', - ); - } - signedCreation = { - policy, - authorization: await verifyNativeCreationAuthorization({ - paths: options.paths, - policyHash: policy.policyHash, - authorization: options.creationAuthorization, - change: options.name, - }), - }; - } + const verificationProtocol = options.verificationProtocol ?? 'legacy-v1'; const changeDir = nativeChangeDir(options.paths, options.name); await resolveContainedNativePath(options.paths.nativeRoot, changeDir); let createdChangeDir = false; @@ -730,26 +684,7 @@ async function createNativeChangeLocked(options: { baseline.policy?.hash ?? null, ); } - let creationBaseline = baseline; - if (signedCreation) { - const policyText = `${JSON.stringify(signedCreation.policy, null, 2)}\n`; - const policySnapshotHash = createHash('sha256').update(policyText).digest('hex'); - const policySnapshotRef = `runtime/trust/review-policy-${signedCreation.policy.policyHash}.json`; - await fs.mkdir(path.join(changeDir, 'runtime', 'trust'), { recursive: true }); - await atomicWriteText(path.join(changeDir, ...policySnapshotRef.split('/')), policyText); - creationBaseline = { - ...baseline, - creation: { - schema: 'comet.native.change-creation-binding.v1', - protocol: 'signed-v2', - policyHash: signedCreation.policy.policyHash, - policySnapshotRef, - policySnapshotHash, - authorization: signedCreation.authorization, - }, - }; - } - await writeNativeBaselineManifest(options.paths, state.name, creationBaseline); + await writeNativeBaselineManifest(options.paths, state.name, baseline); await createNativeChangeFile(options.paths, state); await writeNativeWorkspaceIdentity({ paths: options.paths, @@ -809,74 +744,10 @@ async function assertNativeVerificationProtocolBinding( paths: NativeProjectPaths, state: NativeChangeState, ): Promise { - const controllerTrust = await readNativeControllerTrustProject(paths.projectRoot); - const controllerRequiresSigned = - controllerTrust !== null && !controllerTrust.legacyChanges.includes(state.name); - if (controllerRequiresSigned && state.verification_protocol !== 'signed-v2') { - throw new Error( - `Native verification protocol does not match its creation baseline (controller-backed): expected signed-v2 for ${state.name}`, - ); - } - if ( - controllerTrust !== null && - controllerTrust.legacyChanges.includes(state.name) && - state.verification_protocol !== 'legacy-v1' - ) { - throw new Error(`Native controller trust marks ${state.name} as legacy-v1`); - } const baseline = await readNativeBaselineManifest(paths, state.name); - if (baseline === null) { - if (state.verification_protocol === 'signed-v2' || controllerRequiresSigned) { - throw new Error( - `Native signed-v2 verification protocol has no creation baseline: ${state.name}`, - ); - } - return; - } - const expectedProtocol: NativeVerificationProtocol = baseline.creation - ? 'signed-v2' - : 'legacy-v1'; - if (state.verification_protocol !== expectedProtocol) { - throw new Error( - `Native verification protocol does not match its creation baseline: expected ${expectedProtocol}, got ${state.verification_protocol}`, - ); - } - if (expectedProtocol === 'signed-v2') { - if (!controllerTrust) { - throw new Error('Native signed-v2 creation has no controller-owned trust root'); - } - const creation = baseline.creation!; - const changeDir = nativeChangeDir(paths, state.name); - const snapshot = await readNativeProtectedTextFile({ - root: nativeChangeDir(paths, state.name), - file: path.join(changeDir, ...creation.policySnapshotRef.split('/')), - maxBytes: 64 * 1024, - label: 'Native creation-time trust policy snapshot', - }); - if (createHash('sha256').update(snapshot.text).digest('hex') !== creation.policySnapshotHash) { - throw new Error('Native creation-time trust policy snapshot hash mismatch'); - } - let policy: unknown; - try { - policy = JSON.parse(snapshot.text); - } catch (error) { - throw new Error('Native creation-time trust policy snapshot is invalid JSON', { - cause: error, - }); - } - const verifiedPolicy = verifyNativeReviewTrustPolicy( - policy, - controllerTrust.controllerIdentity, - ); - if (verifiedPolicy.policyHash !== creation.policyHash) { - throw new Error('Native creation-time trust policy snapshot does not match its binding'); - } - await verifyNativeCreationAuthorization({ - paths, - policyHash: creation.policyHash, - authorization: creation.authorization, - change: state.name, - }); + if (baseline === null) return; + if (state.verification_protocol !== 'legacy-v1') { + throw new Error(`Native verification protocol is unsupported: ${state.verification_protocol}`); } } diff --git a/domains/comet-native/native-cli.ts b/domains/comet-native/native-cli.ts index 0d988e49..6096ee38 100644 --- a/domains/comet-native/native-cli.ts +++ b/domains/comet-native/native-cli.ts @@ -49,14 +49,7 @@ import { import { readNativeBoundedTextFile } from './native-bounded-file.js'; import { NATIVE_CONTRACT_FILE_LIMITS } from './native-contract-files.js'; import { MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES } from './native-verification-scope.js'; -import { - buildNativeCreationAuthorization, - parseNativeCreationAuthorization, -} from './native-creation-authorization.js'; -import { - nativeControllerProjectRootHash, - readNativeControllerTrustProject, -} from './native-controller-trust.js'; +import { readNativeControllerTrustProject } from './native-controller-trust.js'; import { approveNativeIndependentReviewPreparation, finalizeNativeImplementationAttestation, @@ -79,7 +72,6 @@ import { import { buildNativeReviewTrustPolicy, NATIVE_REVIEW_TRUST_POLICY_REF, - readNativeReviewTrustPolicy, } from './native-review-trust.js'; import { parseNativeImplementationPreparation, @@ -126,7 +118,7 @@ Commands: init [--root ] [--language en|zh-CN] root show root move - new --creation-authorization [--language en|zh-CN] + new [--language en|zh-CN] spec remove spec rebase --summary list [--cursor ] @@ -139,7 +131,6 @@ Commands: trust keygen --identity --private-key trust identity --private-key-env --identity trust policy --implementation-identity --reviewer-identity --waiver-identity --controller-private-key-env - trust authorize --controller-private-key-env --output receipt manual --acceptance --responsible --step --observation --confirmed receipt automated --acceptance [--timeout-ms ] -- [args...] receipt implement prepare --identity --output @@ -497,10 +488,6 @@ async function dispatch( } if (command === 'new') { const name = requiredPositional(rawArgs, 'change name'); - const creationAuthorizationPath = takeOption(rawArgs, '--creation-authorization'); - if (!creationAuthorizationPath) { - throw new NativeUsageError('--creation-authorization is required'); - } let config = await readProjectConfig(projectRoot); const language = languageOption(rawArgs, config?.native.language ?? 'en'); assertNoArguments(rawArgs); @@ -518,10 +505,6 @@ async function dispatch( paths, name, language, - verificationProtocol: 'signed-v2', - creationAuthorization: parseNativeCreationAuthorization( - await readEvidenceJson(creationAuthorizationPath, 'Native creation authorization'), - ), }); await selectNativeChange(paths, state.name); const status = await inspectNativeStatus(paths, state.name, { @@ -855,13 +838,6 @@ async function dispatch( const policyPath = path.join(projectRoot, ...NATIVE_REVIEW_TRUST_POLICY_REF.split('/')); await withNativeMutationLock(paths, 'create review trust policy', async () => { await resolveContainedNativePath(projectRoot, policyPath); - const activeEntries = await fs.readdir(paths.changesDir).catch((error: unknown) => { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return []; - throw error; - }); - if (activeEntries.length > 0) { - throw new Error('Native review trust policy must be created before any active change'); - } await atomicWriteJson(policyPath, policy, { containedRoot: projectRoot, exclusive: true, @@ -873,40 +849,6 @@ async function dispatch( `Native review trust policy created: ${NATIVE_REVIEW_TRUST_POLICY_REF}\n`, ); } - if (subcommand === 'authorize') { - const name = requiredPositional(rawArgs, 'change name'); - const controllerPrivateKeyEnv = takeOption(rawArgs, '--controller-private-key-env'); - const outputPathValue = takeOption(rawArgs, '--output'); - if (!controllerPrivateKeyEnv || !outputPathValue) { - throw new NativeUsageError( - 'trust authorize requires --controller-private-key-env and --output', - ); - } - assertNoArguments(rawArgs); - const { paths } = await configuredPaths(projectRoot); - const [controllerTrust, policy, projectRootHash] = await Promise.all([ - readNativeControllerTrustProject(projectRoot), - readNativeReviewTrustPolicy(paths), - nativeControllerProjectRootHash(projectRoot), - ]); - if (!controllerTrust) { - throw new Error('Native project has no controller-owned trust root'); - } - const authorization = buildNativeCreationAuthorization({ - controllerIdentity: controllerTrust.controllerIdentity, - controllerPrivateKey: privateKeyFromEnvironment(controllerPrivateKeyEnv), - projectRootHash, - policyHash: policy.policyHash, - change: name, - }); - const outputPath = path.resolve(outputPathValue); - await writeExclusiveFile(outputPath, `${JSON.stringify(authorization, null, 2)}\n`, 0o644); - return success( - 'trust authorize', - { outputPath, authorization }, - `Native creation authorization written to ${outputPath}\n`, - ); - } throw new NativeUsageError(`Unknown trust command: ${subcommand}`); } if (command === 'receipt') { diff --git a/domains/comet-native/native-controller-trust.ts b/domains/comet-native/native-controller-trust.ts index e876a891..cb018a9e 100644 --- a/domains/comet-native/native-controller-trust.ts +++ b/domains/comet-native/native-controller-trust.ts @@ -15,12 +15,10 @@ export const NATIVE_CONTROLLER_TRUST_STORE_TEST_ENV = const PROJECT_ROOT_HASH_TAG = 'comet.native.controller-project-root.v1'; const MAX_STORE_BYTES = 256 * 1024; const HASH_PATTERN = /^[a-f0-9]{64}$/u; -const CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; export interface NativeControllerTrustProject { projectRootHash: string; controllerIdentity: NativeReviewIdentity; - legacyChanges: string[]; } export interface NativeControllerTrustStore { @@ -89,34 +87,18 @@ export function parseNativeControllerTrustStore(value: unknown): NativeControlle const project = record(value, `Native controller trust project ${index}`); exactKeys( project, - ['projectRootHash', 'controllerIdentity', 'legacyChanges'], + ['projectRootHash', 'controllerIdentity'], `Native controller trust project ${index}`, ); if ( typeof project.projectRootHash !== 'string' || - !HASH_PATTERN.test(project.projectRootHash) || - !Array.isArray(project.legacyChanges) + !HASH_PATTERN.test(project.projectRootHash) ) { throw new Error(`Native controller trust project ${index} is invalid`); } - const legacyChanges = project.legacyChanges.map((change) => { - if (typeof change !== 'string' || !CHANGE_NAME_PATTERN.test(change)) { - throw new Error(`Native controller trust project ${index} legacy change is invalid`); - } - return change; - }); - if ( - JSON.stringify(legacyChanges) !== - JSON.stringify( - [...new Set(legacyChanges)].sort((left, right) => left.localeCompare(right, 'en')), - ) - ) { - throw new Error(`Native controller trust project ${index} legacy changes must be sorted`); - } return { projectRootHash: project.projectRootHash, controllerIdentity: parseNativeReviewIdentity(project.controllerIdentity), - legacyChanges, }; }); if ( diff --git a/domains/comet-native/native-creation-authorization.ts b/domains/comet-native/native-creation-authorization.ts deleted file mode 100644 index 45a1e5d1..00000000 --- a/domains/comet-native/native-creation-authorization.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { canonicalHash } from './native-canonical-hash.js'; -import { - nativeControllerProjectRootHash, - readNativeControllerTrustProject, -} from './native-controller-trust.js'; -import { - parseNativeReviewSignature, - signNativeReviewPayloadHash, - verifyNativeReviewPayloadHash, - type NativeReviewIdentity, - type NativeReviewSignature, -} from './native-review-identity.js'; -import type { NativeProjectPaths } from './native-types.js'; - -export const NATIVE_CREATION_AUTHORIZATION_SCHEMA = - 'comet.native.creation-authorization.v1' as const; -const HASH_PATTERN = /^[a-f0-9]{64}$/u; -const CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; - -export interface NativeCreationAuthorization { - schema: typeof NATIVE_CREATION_AUTHORIZATION_SCHEMA; - controllerKeyId: string; - projectRootHash: string; - policyHash: string; - protocol: 'signed-v2'; - change: string; - issuedAt: string; - authorizationHash: string; - controllerSignature: NativeReviewSignature; -} - -function record(value: unknown, label: string): Record { - if (!value || typeof value !== 'object' || Array.isArray(value)) { - throw new Error(`${label} must be an object`); - } - return value as Record; -} - -function exactKeys( - value: Record, - expected: readonly string[], - label: string, -): void { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - if (JSON.stringify(actual) !== JSON.stringify(canonical)) { - throw new Error(`${label} fields are invalid`); - } -} - -export function buildNativeCreationAuthorization(input: { - controllerIdentity: NativeReviewIdentity; - controllerPrivateKey: string; - projectRootHash: string; - policyHash: string; - change: string; - now?: Date; -}): NativeCreationAuthorization { - const content = { - schema: NATIVE_CREATION_AUTHORIZATION_SCHEMA, - controllerKeyId: input.controllerIdentity.keyId, - projectRootHash: input.projectRootHash, - policyHash: input.policyHash, - protocol: 'signed-v2' as const, - change: input.change, - issuedAt: (input.now ?? new Date()).toISOString(), - }; - const authorizationHash = canonicalHash(NATIVE_CREATION_AUTHORIZATION_SCHEMA, content); - return parseNativeCreationAuthorization({ - ...content, - authorizationHash, - controllerSignature: signNativeReviewPayloadHash({ - identity: input.controllerIdentity, - privateKey: input.controllerPrivateKey, - payloadHash: authorizationHash, - }), - }); -} - -export function parseNativeCreationAuthorization(value: unknown): NativeCreationAuthorization { - const root = record(value, 'Native creation authorization'); - exactKeys( - root, - [ - 'schema', - 'controllerKeyId', - 'projectRootHash', - 'policyHash', - 'protocol', - 'change', - 'issuedAt', - 'authorizationHash', - 'controllerSignature', - ], - 'Native creation authorization', - ); - if ( - root.schema !== NATIVE_CREATION_AUTHORIZATION_SCHEMA || - typeof root.controllerKeyId !== 'string' || - !HASH_PATTERN.test(root.controllerKeyId) || - typeof root.projectRootHash !== 'string' || - !HASH_PATTERN.test(root.projectRootHash) || - typeof root.policyHash !== 'string' || - !HASH_PATTERN.test(root.policyHash) || - root.protocol !== 'signed-v2' || - typeof root.change !== 'string' || - !CHANGE_NAME_PATTERN.test(root.change) || - typeof root.issuedAt !== 'string' || - Number.isNaN(Date.parse(root.issuedAt)) || - typeof root.authorizationHash !== 'string' || - !HASH_PATTERN.test(root.authorizationHash) - ) { - throw new Error('Native creation authorization is invalid'); - } - const content = { - schema: NATIVE_CREATION_AUTHORIZATION_SCHEMA, - controllerKeyId: root.controllerKeyId, - projectRootHash: root.projectRootHash, - policyHash: root.policyHash, - protocol: 'signed-v2' as const, - change: root.change, - issuedAt: root.issuedAt, - }; - const authorizationHash = canonicalHash(NATIVE_CREATION_AUTHORIZATION_SCHEMA, content); - if (authorizationHash !== root.authorizationHash) { - throw new Error('Native creation authorization hash mismatch'); - } - const controllerSignature = parseNativeReviewSignature(root.controllerSignature); - if ( - controllerSignature.keyId !== root.controllerKeyId || - controllerSignature.payloadHash !== authorizationHash - ) { - throw new Error('Native creation authorization signature binding is invalid'); - } - return { ...content, authorizationHash, controllerSignature }; -} - -export async function verifyNativeCreationAuthorization(options: { - paths: NativeProjectPaths; - policyHash: string; - authorization: unknown; - change: string; -}): Promise { - const controllerTrust = await readNativeControllerTrustProject(options.paths.projectRoot); - if (!controllerTrust) { - throw new Error('Native project has no controller-owned trust root'); - } - const authorization = parseNativeCreationAuthorization(options.authorization); - const projectRootHash = await nativeControllerProjectRootHash(options.paths.projectRoot); - if ( - authorization.controllerKeyId !== controllerTrust.controllerIdentity.keyId || - authorization.projectRootHash !== projectRootHash || - authorization.policyHash !== options.policyHash || - authorization.change !== options.change - ) { - throw new Error('Native creation authorization does not match the trusted project/change'); - } - verifyNativeReviewPayloadHash({ - identity: controllerTrust.controllerIdentity, - payloadHash: authorization.authorizationHash, - proof: authorization.controllerSignature, - }); - return authorization; -} diff --git a/domains/comet-native/native-review-trust.ts b/domains/comet-native/native-review-trust.ts index 11360db0..de30e95c 100644 --- a/domains/comet-native/native-review-trust.ts +++ b/domains/comet-native/native-review-trust.ts @@ -179,7 +179,7 @@ export function verifyNativeReviewTrustPolicy( return policy; } -/** Read the public trust policy before opening a signed-v2 change. */ +/** Read the public trust policy when external review evidence is required. */ export async function readNativeReviewTrustPolicy( paths: NativeProjectPaths, ): Promise { diff --git a/domains/comet-native/native-snapshot.ts b/domains/comet-native/native-snapshot.ts index 4c0da1a8..0a698749 100644 --- a/domains/comet-native/native-snapshot.ts +++ b/domains/comet-native/native-snapshot.ts @@ -4,7 +4,6 @@ import { promises as fs } from 'fs'; import path from 'path'; import { atomicWriteJson } from './native-atomic-file.js'; -import { parseNativeCreationAuthorization } from './native-creation-authorization.js'; import { DEFAULT_NATIVE_SNAPSHOT_CONFIG, normalizeNativeSnapshotPattern, @@ -41,7 +40,6 @@ const CHANGE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u; const MANIFEST_KEYS = new Set([ 'schema', 'origin', - 'creation', 'capture', 'createdAt', 'complete', @@ -61,14 +59,6 @@ const LIMIT_KEYS = new Set([ ]); const POLICY_KEYS = new Set(['schema', 'include', 'exclude', 'hash']); const CAPTURE_KEYS = new Set(['provider', 'gitSelection', 'physicalSelection', 'projection']); -const CREATION_KEYS = new Set([ - 'schema', - 'protocol', - 'policyHash', - 'policySnapshotRef', - 'policySnapshotHash', - 'authorization', -]); const GIT_PROJECTION_KEYS = new Set(['provider', 'selection']); const GIT_SELECTION_KEYS = new Set([ 'schema', @@ -1898,34 +1888,6 @@ export function parseNativeContentSnapshotManifest(value: unknown): NativeConten if (!SNAPSHOT_ORIGINS.has(manifest.origin as NativeContentSnapshotManifest['origin'])) { throw new Error('Native content snapshot origin is invalid'); } - let creation: NativeContentSnapshotManifest['creation']; - if (manifest.creation !== undefined) { - const value = record(manifest.creation, 'Native change creation binding'); - rejectUnknown(value, CREATION_KEYS, 'Native change creation binding'); - if ( - value.schema !== 'comet.native.change-creation-binding.v1' || - value.protocol !== 'signed-v2' || - typeof value.policyHash !== 'string' || - !HASH_PATTERN.test(value.policyHash) || - typeof value.policySnapshotHash !== 'string' || - !HASH_PATTERN.test(value.policySnapshotHash) || - typeof value.policySnapshotRef !== 'string' || - !/^runtime\/trust\/review-policy-[a-f0-9]{64}\.json$/u.test(value.policySnapshotRef) - ) { - throw new Error('Native change creation binding is invalid'); - } - creation = { - schema: 'comet.native.change-creation-binding.v1', - protocol: 'signed-v2', - policyHash: value.policyHash, - policySnapshotRef: value.policySnapshotRef, - policySnapshotHash: value.policySnapshotHash, - authorization: parseNativeCreationAuthorization(value.authorization), - }; - } - if (manifest.origin !== 'change-created' && creation !== undefined) { - throw new Error('Native change creation binding does not match snapshot origin'); - } let capture: NativeContentSnapshotManifest['capture']; if (manifest.capture !== undefined) { const captureValue = record(manifest.capture, 'Native content snapshot capture'); @@ -2110,7 +2072,6 @@ export function parseNativeContentSnapshotManifest(value: unknown): NativeConten const parsed: NativeContentSnapshotManifest = { schema: 'comet.native.content-snapshot.v1', origin: manifest.origin as NativeContentSnapshotManifest['origin'], - ...(creation ? { creation } : {}), ...(capture ? { capture } : {}), createdAt: manifest.createdAt, complete: manifest.complete, diff --git a/domains/comet-native/native-types.ts b/domains/comet-native/native-types.ts index 2d1a9a77..52a630c0 100644 --- a/domains/comet-native/native-types.ts +++ b/domains/comet-native/native-types.ts @@ -12,7 +12,7 @@ export type NativeVerificationResult = 'pending' | 'pass' | 'fail'; export type NativeSpecOperation = 'create' | 'replace' | 'remove'; export type NativeClarificationMode = WorkflowNativeEnabledProjectConfig['native']['clarification_mode']; -export type NativeVerificationProtocol = 'legacy-v1' | 'signed-v2'; +export type NativeVerificationProtocol = 'legacy-v1'; export const NATIVE_RUNTIME_PROTOCOL_VERSION = 3 as const; export const NATIVE_CHANGE_SCHEMA = 'comet.native.v3' as const; @@ -209,14 +209,6 @@ export type NativeContentSnapshotCapture = export interface NativeContentSnapshotManifest { schema: 'comet.native.content-snapshot.v1'; origin: 'change-created' | 'legacy-migration' | 'explicit'; - creation?: { - schema: 'comet.native.change-creation-binding.v1'; - protocol: 'signed-v2'; - policyHash: string; - policySnapshotRef: string; - policySnapshotHash: string; - authorization: NativeCreationAuthorization; - }; capture?: NativeContentSnapshotCapture; createdAt: string; complete: boolean; @@ -678,4 +670,3 @@ export interface NativeTransactionHooks { ) => void | Promise; } import type { RunState } from '../engine/types.js'; -import type { NativeCreationAuthorization } from './native-creation-authorization.js'; diff --git a/domains/comet-native/native-verification-runtime.ts b/domains/comet-native/native-verification-runtime.ts index 80ccc30d..2b8aaab5 100644 --- a/domains/comet-native/native-verification-runtime.ts +++ b/domains/comet-native/native-verification-runtime.ts @@ -652,9 +652,10 @@ export async function inspectNativeVerificationEvidence( contractHash: facts.contractHash, implementationScope: facts.bundle, }); - const signedVerification = options.state.verification_protocol === 'signed-v2'; + const independentReviewRequired = + !facts.bundle.scope.complete || isNativeHighRiskScope(facts.bundle.scope.changes); if ( - signedVerification && + independentReviewRequired && options.result === 'pass' && receiptGraph.independentReviewReceiptRef === null ) { @@ -845,7 +846,7 @@ export async function inspectNativeVerificationFreshness(options: { findingCodes.push('verification-receipt-invalid'); } if ( - options.state.verification_protocol === 'signed-v2' && + (!facts.bundle.scope.complete || isNativeHighRiskScope(facts.bundle.scope.changes)) && envelope.result === 'pass' && envelope.independentReviewReceiptRef === null ) { diff --git a/domains/skill/platform-install.ts b/domains/skill/platform-install.ts index 277a7f9c..4b6bacb3 100644 --- a/domains/skill/platform-install.ts +++ b/domains/skill/platform-install.ts @@ -1588,7 +1588,7 @@ function managedConfigFields(language: string = 'en'): ManagedConfigFields { const classic: ManagedConfigField[] = [ { key: 'artifact_layout', - def: 'legacy', + def: 'docs', comment: projectConfigComment('classic.artifact_layout', commentLanguage), }, { @@ -1618,6 +1618,16 @@ function managedConfigFields(language: string = 'en'): ManagedConfigFields { def: 'sequential', comment: projectConfigComment('native.clarification_mode', commentLanguage), }, + { + key: 'archive_confirmation', + def: 'automatic', + comment: projectConfigComment('native.archive_confirmation', commentLanguage), + }, + { + key: 'max_verify_failures', + def: '5', + comment: projectConfigComment('native.max_verify_failures', commentLanguage), + }, ]; return { top, native, classic }; } @@ -1651,6 +1661,7 @@ function parseProjectConfigOverrides(content: string): Record { function coerceConfigScalar(raw: unknown): unknown { if (raw === 'true') return true; if (raw === 'false') return false; + if (typeof raw === 'string' && /^(?:0|[1-9]\d*)$/u.test(raw)) return Number(raw); return raw; } @@ -1688,7 +1699,7 @@ function renderProjectConfig( async function mergeProjectConfig( projectPath: string, language: string | null = null, - artifactLayoutDefault: 'legacy' | 'docs' = 'legacy', + artifactLayoutDefault: 'legacy' | 'docs' = 'docs', completeProjectConfig = false, enableClassicWorkflow = completeProjectConfig, ): Promise { @@ -1748,6 +1759,16 @@ async function mergeProjectConfig( if (f.key === 'clarification_mode' && value !== 'sequential' && value !== 'batch') { throw new Error('native.clarification_mode must be sequential or batch'); } + if (f.key === 'archive_confirmation' && value !== 'automatic' && value !== 'required') { + throw new Error('native.archive_confirmation must be automatic or required'); + } + if ( + f.key === 'max_verify_failures' && + (!Number.isSafeInteger(coerceConfigScalar(value)) || + (coerceConfigScalar(value) as number) < 1) + ) { + throw new Error('native.max_verify_failures must be a positive integer'); + } nativeBlock[f.key] = coerceConfigScalar(value); } if (nativeBlock.snapshot === undefined) { diff --git a/domains/workflow-contract/project-config.ts b/domains/workflow-contract/project-config.ts index 81a9ca86..7a9744ef 100644 --- a/domains/workflow-contract/project-config.ts +++ b/domains/workflow-contract/project-config.ts @@ -83,7 +83,7 @@ const COMMENTS: Record None: - """Provision controller-owned signed-v2 trust without exposing a controller private key.""" + """Provision external review trust without exposing private keys.""" marker = environment_dir / TRUSTED_NATIVE_REVIEW_FIXTURE_MARKER if not marker.is_file(): return @@ -563,35 +563,15 @@ def _copy_trusted_native_review_fixture( controllerSignature:proof(controller,policyHash) }; const projectRootHash=hash('comet.native.controller-project-root.v1','/workspace'); -const authorizationContent={ - schema:'comet.native.creation-authorization.v1', - controllerKeyId:controller.identity.keyId, - projectRootHash, - policyHash, - protocol:'signed-v2', - change, - issuedAt:'2026-07-28T00:00:00.000Z' -}; -const authorizationHash=hash( - 'comet.native.creation-authorization.v1', - authorizationContent -); -const authorization={ - ...authorizationContent, - authorizationHash, - controllerSignature:proof(controller,authorizationHash) -}; const store={ schema:'comet.native.controller-trust-store.v1', projects:[{ projectRootHash, controllerIdentity:controller.identity, - legacyChanges:[] }] }; process.stdout.write(JSON.stringify({ policy, - authorization, store, identities:{ implementation:implementation.identity, @@ -625,10 +605,6 @@ def _copy_trusted_native_review_fixture( project_policy = test_dir / ".comet" / "native-review-trust.json" project_policy.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(policy_file, project_policy) - authorization_name = f"{change}-creation-authorization.json" - (oracle_root / authorization_name).write_text( - json.dumps(fixture["authorization"], indent=2) + "\n", encoding="utf-8" - ) for role, identity in fixture["identities"].items(): (oracle_root / f"{role}-identity.json").write_text( json.dumps(identity, indent=2) + "\n", encoding="utf-8" @@ -688,7 +664,6 @@ def _copy_trusted_native_review_fixture( "schema": TRUSTED_NATIVE_REVIEW_FIXTURE_SCHEMA, "change": change, "policyHash": fixture["policy"]["policyHash"], - "creationAuthorization": f"/workspace/_eval_trusted_oracles/{authorization_name}", "signer": "/workspace/_eval_trusted_oracles/native-review-signer.mjs", "signerMode": "external-verifier-sidecar", "roles": ["implementation", "reviewer", "waiver"], diff --git a/eval/local/tests/scaffold/test_native_wave_evaluations.py b/eval/local/tests/scaffold/test_native_wave_evaluations.py index 36135847..34455eaa 100644 --- a/eval/local/tests/scaffold/test_native_wave_evaluations.py +++ b/eval/local/tests/scaffold/test_native_wave_evaluations.py @@ -597,11 +597,6 @@ def test_controller_provisions_immutable_native_review_fixture(tmp_path: Path): (oracle / "native-review-fixture.json").read_text(encoding="utf-8") ) policy = json.loads((oracle / "native-review-trust.json").read_text(encoding="utf-8")) - authorization = json.loads( - (oracle / "sentence-counting-creation-authorization.json").read_text( - encoding="utf-8" - ) - ) store = json.loads( (oracle / "controller-home/native-controller-trust.json").read_text( encoding="utf-8" @@ -611,16 +606,13 @@ def test_controller_provisions_immutable_native_review_fixture(tmp_path: Path): assert public_fixture["change"] == "sentence-counting" assert public_fixture["signerMode"] == "external-verifier-sidecar" assert policy["schema"] == "comet.native.review-trust-policy.v2" - assert authorization["schema"] == "comet.native.creation-authorization.v1" - assert authorization["policyHash"] == policy["policyHash"] - assert authorization["projectRootHash"] == canonical_hash( + project_root_hash = canonical_hash( "comet.native.controller-project-root.v1", "/workspace" ) assert store["projects"] == [ { - "projectRootHash": authorization["projectRootHash"], + "projectRootHash": project_root_hash, "controllerIdentity": store["projects"][0]["controllerIdentity"], - "legacyChanges": [], } ] assert (workspace / ".comet/native-review-trust.json").read_bytes() == ( @@ -633,13 +625,6 @@ def test_controller_provisions_immutable_native_review_fixture(tmp_path: Path): policy["policyHash"], "Controller policy", ) - validator._validate_signature( - authorization["controllerSignature"], - store["projects"][0]["controllerIdentity"], - authorization["authorizationHash"], - "Controller authorization", - ) - secrets_root = workspace.with_name(f".{workspace.name}-native-review-secrets") secret_documents = [ json.loads((secrets_root / f"{role}-key.json").read_text(encoding="utf-8")) diff --git a/package-lock.json b/package-lock.json index 75b06cce..61e1373a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rpamis/comet", - "version": "0.4.0-beta.10", + "version": "0.4.0-beta.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rpamis/comet", - "version": "0.4.0-beta.10", + "version": "0.4.0-beta.11", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 0ef47e38..1feabe63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rpamis/comet", - "version": "0.4.0-beta.10", + "version": "0.4.0-beta.11", "description": "Agent Skill Harness For Turning Ideas Into Evaluated Workflows", "keywords": [ "comet", diff --git a/test/app/cli-help.test.ts b/test/app/cli-help.test.ts index 5b0b688c..fd5ba01f 100644 --- a/test/app/cli-help.test.ts +++ b/test/app/cli-help.test.ts @@ -29,7 +29,7 @@ describe('CLI help text', () => { expect(help.status, help.stderr).toBe(0); expect(help.stdout).toContain(tagline); expect(packageJson.description).toBe(tagline); - expect(packageJson.version).toBe('0.4.0-beta.10'); + expect(packageJson.version).toBe('0.4.0-beta.11'); }); it('marks bundle as the advanced backend and skill Engine runs as advanced', () => { diff --git a/test/app/init-e2e.test.ts b/test/app/init-e2e.test.ts index 0d7c4b9a..ccc0d204 100644 --- a/test/app/init-e2e.test.ts +++ b/test/app/init-e2e.test.ts @@ -366,6 +366,12 @@ describe('comet init E2E', () => { await fs.mkdir(path.join(tmpDir, '.claude'), { recursive: true }); await fs.mkdir(path.join(tmpDir, '.comet'), { recursive: true }); await fs.writeFile(path.join(tmpDir, '.comet', 'config.yaml'), 'language: en\n', 'utf8'); + await fs.mkdir(path.join(tmpDir, 'openspec'), { recursive: true }); + await fs.writeFile( + path.join(tmpDir, 'openspec', 'config.yaml'), + 'schema: spec-driven\n', + 'utf8', + ); const { initCommand } = await import('../../app/commands/init.js'); const result = await captureJsonOutput(() => initCommand(tmpDir, { yes: true, json: true })); diff --git a/test/app/update.test.ts b/test/app/update.test.ts index c064109a..a49ddd9f 100644 --- a/test/app/update.test.ts +++ b/test/app/update.test.ts @@ -2119,6 +2119,98 @@ describe('update command helpers', () => { }); }); + it('backfills every managed Native and Classic default with the docs layout', async () => { + const fakeHome = path.join(tmpDir, 'partial-config-defaults-home'); + await fs.mkdir(path.join(tmpDir, '.comet'), { recursive: true }); + await fs.writeFile( + path.join(tmpDir, '.comet', 'config.yaml'), + [ + 'default_workflow: native', + 'native:', + ' artifact_root: docs', + ' language: en', + 'custom_top: keep', + '', + ].join('\n'), + 'utf8', + ); + await fs.mkdir(path.join(tmpDir, '.claude', 'skills', 'comet'), { recursive: true }); + await fs.writeFile( + path.join(tmpDir, '.claude', 'skills', 'comet', 'SKILL.md'), + '# Comet\n', + 'utf8', + ); + await fs.mkdir(path.join(tmpDir, '.claude', 'skills', 'openspec-propose'), { + recursive: true, + }); + await fs.writeFile( + path.join(tmpDir, '.claude', 'skills', 'openspec-propose', 'SKILL.md'), + '# OpenSpec\n', + 'utf8', + ); + + const homeSpy = vi.spyOn(os, 'homedir').mockReturnValue(fakeHome); + const log = vi.spyOn(console, 'log').mockImplementation(() => undefined); + let json: string; + try { + await updateCommand(tmpDir, { + currentProject: true, + installMode: 'copy', + language: 'en', + json: true, + skipNpm: true, + }); + json = log.mock.calls.map((call) => call.join(' ')).join('\n'); + } finally { + log.mockRestore(); + homeSpy.mockRestore(); + } + + expect(JSON.parse(json)).toMatchObject({ status: 'complete' }); + const config = parse(await fs.readFile(path.join(tmpDir, '.comet', 'config.yaml'), 'utf8')); + expect(config).toMatchObject({ + schema: 'comet.project.v1', + default_workflow: 'native', + workflows: ['native', 'classic'], + ambient_resume: true, + native: { + artifact_root: 'docs', + language: 'en', + clarification_mode: 'sequential', + archive_confirmation: 'automatic', + max_verify_failures: 5, + snapshot: { + include: ['**/*'], + exclude: [], + max_files: 10_000, + max_total_bytes: 256 * 1024 * 1024, + max_duration_ms: 60_000, + }, + }, + classic: { + artifact_layout: 'docs', + language: 'en', + context_compression: 'off', + review_mode: 'standard', + auto_transition: true, + }, + custom_top: 'keep', + }); + await expect(assertClassicLayoutReadable(tmpDir)).resolves.toMatchObject({ + artifactLayout: 'docs', + openSpecRoot: path.join(tmpDir, 'docs', 'openspec'), + }); + expect(mockedInstallOpenSpec).toHaveBeenCalledWith( + tmpDir, + ['claude'], + 'project', + false, + [], + 'docs', + expect.any(Function), + ); + }); + it('fails closed without invoking OpenSpec or mutating either artifact root when both roots exist', async () => { const fakeHome = path.join(tmpDir, 'classic-dual-root-update-home'); await arrangeClassicDocsOpenSpecUpdate(tmpDir); diff --git a/test/domains/comet-classic/classic-layout.test.ts b/test/domains/comet-classic/classic-layout.test.ts index 11685b69..fe6c31f1 100644 --- a/test/domains/comet-classic/classic-layout.test.ts +++ b/test/domains/comet-classic/classic-layout.test.ts @@ -75,13 +75,13 @@ describe('Classic artifact layout', () => { ); }); - it('defaults a missing Classic layout to legacy', async () => { + it('defaults a missing Classic layout to docs', async () => { const root = await project(); await config(root, ' language: zh-CN\n'); - await expect(readClassicArtifactLayout(root)).resolves.toBe('legacy'); - expect(classicLayoutPaths(root, 'legacy').changesDir).toBe( - path.join(root, 'openspec', 'changes'), + await expect(readClassicArtifactLayout(root)).resolves.toBe('docs'); + expect(classicLayoutPaths(root, 'docs').changesDir).toBe( + path.join(root, 'docs', 'openspec', 'changes'), ); }); diff --git a/test/domains/comet-entry/init-workflow.test.ts b/test/domains/comet-entry/init-workflow.test.ts index 8b643a4c..daf2ef10 100644 --- a/test/domains/comet-entry/init-workflow.test.ts +++ b/test/domains/comet-entry/init-workflow.test.ts @@ -174,7 +174,7 @@ describe('Comet init workflow policy', () => { workflow: 'native', source: 'explicit-option', artifactRoot: 'docs', - classicArtifactLayout: 'legacy', + classicArtifactLayout: 'docs', writeProjectConfig: true, }, ); @@ -202,7 +202,7 @@ describe('Comet init workflow policy', () => { workflow, source: 'project-config', artifactRoot: 'docs', - classicArtifactLayout: workflow === 'classic' ? 'legacy' : 'docs', + classicArtifactLayout: 'docs', writeProjectConfig: false, legacyEvidence: [], }); diff --git a/test/domains/comet-native/native-change.test.ts b/test/domains/comet-native/native-change.test.ts index c4fe1873..b436d9d2 100644 --- a/test/domains/comet-native/native-change.test.ts +++ b/test/domains/comet-native/native-change.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { createHash } from 'node:crypto'; import { promises as fs } from 'fs'; import os from 'os'; import path from 'path'; @@ -18,97 +17,40 @@ import { readNativeChange, writeNativeChange, } from '../../../domains/comet-native/native-change.js'; -import { inspectNativeArchivePreflight } from '../../../domains/comet-native/native-archive-inspection.js'; -import { inspectNativeStatus } from '../../../domains/comet-native/native-diagnostics.js'; import { nativeProjectPaths } from '../../../domains/comet-native/native-paths.js'; -import { generateNativeReviewKeyPair } from '../../../domains/comet-native/native-review-identity.js'; -import { buildNativeReviewTrustPolicy } from '../../../domains/comet-native/native-review-trust.js'; import { readNativeBaselineManifest } from '../../../domains/comet-native/native-snapshot.js'; -import { advanceNativeChange } from '../../../domains/comet-native/native-transitions.js'; import { NATIVE_CHANGE_SCHEMA, NATIVE_RUNTIME_PROTOCOL_VERSION, type NativeProjectPaths, } from '../../../domains/comet-native/native-types.js'; -import { - authorizeNativeTestChange, - installNativeControllerTrust, -} from '../../helpers/native-controller-trust.js'; describe('Native change store', () => { let projectRoot: string; let paths: NativeProjectPaths; - const controller = generateNativeReviewKeyPair(); - let cleanupControllerTrust: (() => Promise) | null; beforeEach(async () => { projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'comet-native-change-')); paths = await nativeProjectPaths(projectRoot, '.'); - cleanupControllerTrust = null; }); afterEach(async () => { - await cleanupControllerTrust?.(); await fs.rm(projectRoot, { recursive: true, force: true }); }); - async function installReviewTrustPolicy(name: string) { - cleanupControllerTrust = await installNativeControllerTrust({ - projectRoot, - controller, - }); - const implementation = generateNativeReviewKeyPair(); - const reviewer = generateNativeReviewKeyPair(); - const waiver = generateNativeReviewKeyPair(); - const policy = buildNativeReviewTrustPolicy({ - controllerIdentity: controller.identity, - controllerPrivateKey: controller.privateKey, - implementationKeyId: implementation.identity.keyId, - trustedReviewers: [reviewer.identity], - trustedWaiverSigners: [waiver.identity], - }); - await fs.mkdir(path.join(projectRoot, '.comet'), { recursive: true }); - await fs.writeFile( - path.join(projectRoot, '.comet', 'native-review-trust.json'), - `${JSON.stringify(policy, null, 2)}\n`, - ); - return authorizeNativeTestChange({ - projectRoot, - controller, - policy, - name, - }); - } - - it('requires a trust policy before creating a default signed-v2 change', async () => { - await expect( - createNativeChange({ - paths, - name: 'missing-trust-policy', - language: 'en', - verificationProtocol: 'signed-v2', - }), - ).rejects.toThrow('require a review trust policy'); - await expect( - fs.access(path.join(paths.changesDir, 'missing-trust-policy')), - ).rejects.toMatchObject({ code: 'ENOENT' }); - }); - it('creates the visible Native change layout without claiming Shape is complete', async () => { - const creationAuthorization = await installReviewTrustPolicy('add-authentication'); const state = await createNativeChange({ paths, name: 'add-authentication', language: 'zh-CN', now: new Date('2026-07-14T00:00:00Z'), - creationAuthorization, }); expect(state).toMatchObject({ schema: NATIVE_CHANGE_SCHEMA, minimum_runtime_version: NATIVE_RUNTIME_PROTOCOL_VERSION, revision: 1, - verification_protocol: 'signed-v2', + verification_protocol: 'legacy-v1', phase: 'shape', approval: null, approved_contract_hash: null, @@ -131,120 +73,8 @@ describe('Native change store', () => { schema: 'comet.native.content-snapshot.v1', origin: 'change-created', complete: true, - entries: [ - expect.objectContaining({ - path: '.comet/native-review-trust.json', - }), - ], - }); - }); - - it('fails status, next, and archive closed when signed-v2 is edited to legacy-v1', async () => { - const creationAuthorization = await installReviewTrustPolicy('downgrade-attempt'); - const state = await createNativeChange({ - paths, - name: 'downgrade-attempt', - language: 'en', - creationAuthorization, + entries: [], }); - const stateFile = path.join(paths.changesDir, state.name, 'comet-state.yaml'); - await fs.writeFile( - stateFile, - (await fs.readFile(stateFile, 'utf8')).replace( - 'verification_protocol: signed-v2', - 'verification_protocol: legacy-v1', - ), - ); - - const status = await inspectNativeStatus(paths, state.name); - expect(status).toMatchObject({ - phase: 'invalid', - nextCommand: null, - archiveReady: false, - error: expect.stringContaining('does not match its creation baseline'), - }); - await expect( - advanceNativeChange({ - paths, - name: state.name, - clarificationMode: 'adaptive', - evidence: { summary: 'Attempt to bypass signed review.' }, - }), - ).rejects.toThrow('does not match its creation baseline'); - await expect(inspectNativeArchivePreflight({ paths, name: state.name })).rejects.toThrow( - 'does not match its creation baseline', - ); - }); - - it('rejects a synchronized state, baseline, policy, and attacker-owner downgrade', async () => { - const creationAuthorization = await installReviewTrustPolicy('forged-downgrade'); - const state = await createNativeChange({ - paths, - name: 'forged-downgrade', - language: 'en', - creationAuthorization, - }); - const attackerController = generateNativeReviewKeyPair(); - const attackerImplementation = generateNativeReviewKeyPair(); - const attackerReviewer = generateNativeReviewKeyPair(); - const attackerWaiver = generateNativeReviewKeyPair(); - const forgedPolicy = buildNativeReviewTrustPolicy({ - controllerIdentity: attackerController.identity, - controllerPrivateKey: attackerController.privateKey, - implementationKeyId: attackerImplementation.identity.keyId, - trustedReviewers: [attackerReviewer.identity], - trustedWaiverSigners: [attackerWaiver.identity], - }); - const forgedPolicyText = `${JSON.stringify(forgedPolicy, null, 2)}\n`; - await fs.writeFile( - path.join(projectRoot, '.comet', 'native-review-trust.json'), - forgedPolicyText, - ); - const baselineFile = path.join( - paths.changesDir, - state.name, - 'runtime', - 'baseline-manifest.json', - ); - const baseline = JSON.parse(await fs.readFile(baselineFile, 'utf8')) as { - origin: string; - creation?: unknown; - entries: Array<{ path: string; hash: string; size: number }>; - }; - baseline.origin = 'legacy-migration'; - delete baseline.creation; - const policyEntry = baseline.entries.find( - (entry) => entry.path === '.comet/native-review-trust.json', - )!; - policyEntry.hash = createHash('sha256').update(forgedPolicyText).digest('hex'); - policyEntry.size = Buffer.byteLength(forgedPolicyText); - await fs.writeFile(baselineFile, `${JSON.stringify(baseline, null, 2)}\n`); - const stateFile = path.join(paths.changesDir, state.name, 'comet-state.yaml'); - await fs.writeFile( - stateFile, - (await fs.readFile(stateFile, 'utf8')).replace( - 'verification_protocol: signed-v2', - 'verification_protocol: legacy-v1', - ), - ); - - const status = await inspectNativeStatus(paths, state.name); - expect(status).toMatchObject({ - phase: 'invalid', - nextCommand: null, - archiveReady: false, - error: expect.stringContaining('controller-backed'), - }); - await expect( - advanceNativeChange({ - paths, - name: state.name, - evidence: { summary: 'Attempt a fully rehashed downgrade.' }, - }), - ).rejects.toThrow('controller-backed'); - await expect(inspectNativeArchivePreflight({ paths, name: state.name })).rejects.toThrow( - 'controller-backed', - ); }); it('fails at change creation when the baseline snapshot is incomplete', async () => { diff --git a/test/domains/comet-native/native-cli.test.ts b/test/domains/comet-native/native-cli.test.ts index ecb3b4ff..44eebd9b 100644 --- a/test/domains/comet-native/native-cli.test.ts +++ b/test/domains/comet-native/native-cli.test.ts @@ -21,10 +21,7 @@ import { generateNativeReviewKeyPair } from '../../../domains/comet-native/nativ import { buildNativeReviewTrustPolicy } from '../../../domains/comet-native/native-review-trust.js'; import type { NativeReviewTrustPolicy } from '../../../domains/comet-native/native-review-trust.js'; import { MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES } from '../../../domains/comet-native/native-verification-scope.js'; -import { - authorizeNativeTestChange, - installNativeControllerTrust, -} from '../../helpers/native-controller-trust.js'; +import { installNativeControllerTrust } from '../../helpers/native-controller-trust.js'; const brief = `# Outcome Add sentence counting. @@ -63,7 +60,6 @@ describe('Comet Native CLI dispatcher', () => { let waiverKey: ReturnType; let controllerKey: ReturnType; let reviewPolicy: NativeReviewTrustPolicy; - let authorizationRoot: string; let cleanupControllerTrust: () => Promise; const projectArgs = () => ['--project-root', projectRoot] as const; @@ -78,7 +74,6 @@ describe('Comet Native CLI dispatcher', () => { projectRoot, controller: controllerKey, }); - authorizationRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'comet-native-authorizations-')); reviewPolicy = buildNativeReviewTrustPolicy({ controllerIdentity: controllerKey.identity, controllerPrivateKey: controllerKey.privateKey, @@ -95,28 +90,9 @@ describe('Comet Native CLI dispatcher', () => { afterEach(async () => { await cleanupControllerTrust(); - await fs.rm(authorizationRoot, { recursive: true, force: true }); await fs.rm(projectRoot, { recursive: true, force: true }); }); - async function creationArgs(name: string): Promise { - const file = path.join(authorizationRoot, `${name}.json`); - await fs.writeFile( - file, - `${JSON.stringify( - await authorizeNativeTestChange({ - projectRoot, - controller: controllerKey, - policy: reviewPolicy, - name, - }), - null, - 2, - )}\n`, - ); - return ['--creation-authorization', file]; - } - it('initializes docs as the default Native artifact root', async () => { const initialized = json(await runNativeCli(['init', '--json', ...projectArgs()])); @@ -136,9 +112,12 @@ describe('Comet Native CLI dispatcher', () => { ).resolves.toContain('artifact_root: docs'); }); - it('creates pre-trusted review identities without printing private keys and formats policy before a change', async () => { + it('creates review identities without printing private keys and can add policy after a change', async () => { await fs.rm(path.join(projectRoot, '.comet', 'native-review-trust.json')); expect((await runNativeCli(['init', ...projectArgs()])).exitCode).toBe(0); + expect( + (await runNativeCli(['new', 'review-policy-after-creation', ...projectArgs()])).exitCode, + ).toBe(0); const secretRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'comet-native-secrets-')); try { const identities = await Promise.all( @@ -269,13 +248,7 @@ describe('Comet Native CLI dispatcher', () => { ); const result = json( - await runNativeCli([ - 'new', - 'incomplete-baseline', - ...(await creationArgs('incomplete-baseline')), - '--json', - ...projectArgs(), - ]), + await runNativeCli(['new', 'incomplete-baseline', '--json', ...projectArgs()]), ); expect(result).toMatchObject({ exitCode: 65, @@ -296,14 +269,7 @@ describe('Comet Native CLI dispatcher', () => { }); it('selects each successfully created change as the current Native owner', async () => { - expect( - await runNativeCli([ - 'new', - 'first-change', - ...(await creationArgs('first-change')), - ...projectArgs(), - ]), - ).toMatchObject({ + expect(await runNativeCli(['new', 'first-change', ...projectArgs()])).toMatchObject({ exitCode: 0, }); expect( @@ -317,14 +283,7 @@ describe('Comet Native CLI dispatcher', () => { branch: null, }); - expect( - await runNativeCli([ - 'new', - 'second-change', - ...(await creationArgs('second-change')), - ...projectArgs(), - ]), - ).toMatchObject({ + expect(await runNativeCli(['new', 'second-change', ...projectArgs()])).toMatchObject({ exitCode: 0, }); expect( @@ -365,12 +324,7 @@ describe('Comet Native CLI dispatcher', () => { const root = json(await runNativeCli(['root', 'show', '--json', ...projectArgs()])); expect(root).toMatchObject({ command: 'root show', data: { artifactRoot: 'docs' } }); - const created = await runNativeCli([ - 'new', - 'sentence-counting', - ...(await creationArgs('sentence-counting')), - ...projectArgs(), - ]); + const created = await runNativeCli(['new', 'sentence-counting', ...projectArgs()]); expect(created).toMatchObject({ exitCode: 0 }); expect(created.stdout).toContain('Created Native change sentence-counting'); const paths = await nativeProjectPaths(projectRoot, 'docs'); @@ -794,12 +748,7 @@ Pass. }, 120_000); it('pages every Runtime-derived acceptance ID through the public status command', async () => { - await runNativeCli([ - 'new', - 'paged-acceptance', - ...(await creationArgs('paged-acceptance')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'paged-acceptance', ...projectArgs()]); const paths = await nativeProjectPaths(projectRoot, 'docs'); const changeDir = path.join(paths.changesDir, 'paged-acceptance'); const acceptanceExamples = Array.from( @@ -920,13 +869,7 @@ Pass. }); it('creates the default config from new and keeps Classic paths untouched', async () => { - const result = await runNativeCli([ - 'new', - 'default-root', - ...(await creationArgs('default-root')), - '--json', - ...projectArgs(), - ]); + const result = await runNativeCli(['new', 'default-root', '--json', ...projectArgs()]); expect(result.exitCode).toBe(0); expect(json(result)).toMatchObject({ data: { name: 'default-root', phase: 'shape' } }); expect(await fs.readFile(path.join(projectRoot, '.comet', 'config.yaml'), 'utf8')).toContain( @@ -975,12 +918,7 @@ Pass. }); it('returns guard findings as structured invalid data', async () => { - await runNativeCli([ - 'new', - 'blocked-shape', - ...(await creationArgs('blocked-shape')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'blocked-shape', ...projectArgs()]); const result = await runNativeCli([ 'next', 'blocked-shape', @@ -998,12 +936,7 @@ Pass. }); it('records explicit confirmation through Shape next without editing change state', async () => { - await runNativeCli([ - 'new', - 'confirmed-shape', - ...(await creationArgs('confirmed-shape')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'confirmed-shape', ...projectArgs()]); const paths = await nativeProjectPaths(projectRoot, 'docs'); const changeDir = path.join(paths.changesDir, 'confirmed-shape'); await fs.writeFile(path.join(changeDir, 'brief.md'), brief); @@ -1028,12 +961,7 @@ Pass. it('enforces shared-understanding confirmation in Sequential and Batch modes', async () => { await runNativeCli(['init', '--root', 'docs', ...projectArgs()]); - await runNativeCli([ - 'new', - 'mode-boundary', - ...(await creationArgs('mode-boundary')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'mode-boundary', ...projectArgs()]); const paths = await nativeProjectPaths(projectRoot, 'docs'); const changeDir = path.join(paths.changesDir, 'mode-boundary'); await fs.writeFile(path.join(changeDir, 'brief.md'), brief); @@ -1129,12 +1057,7 @@ Pass. }); it('records a remove intent and canonical hash through the spec command', async () => { - await runNativeCli([ - 'new', - 'remove-capability', - ...(await creationArgs('remove-capability')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'remove-capability', ...projectArgs()]); const paths = await nativeProjectPaths(projectRoot, 'docs'); const canonical = path.join(paths.specsDir, 'legacy-capability', 'spec.md'); await fs.mkdir(path.dirname(canonical), { recursive: true }); @@ -1166,12 +1089,7 @@ Pass. }); it('rejects show when the brief exceeds its bounded-read budget', async () => { - await runNativeCli([ - 'new', - 'oversized-brief', - ...(await creationArgs('oversized-brief')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'oversized-brief', ...projectArgs()]); const paths = await nativeProjectPaths(projectRoot, 'docs'); await fs.writeFile( path.join(paths.changesDir, 'oversized-brief', 'brief.md'), @@ -1187,12 +1105,7 @@ Pass. }); it('rejects show when proposed specs exceed the count budget', async () => { - await runNativeCli([ - 'new', - 'too-many-specs', - ...(await creationArgs('too-many-specs')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'too-many-specs', ...projectArgs()]); const paths = await nativeProjectPaths(projectRoot, 'docs'); const specsDir = path.join(paths.changesDir, 'too-many-specs', 'specs'); await Promise.all( @@ -1212,12 +1125,7 @@ Pass. }); it('rejects show when proposed specs exceed the aggregate byte budget', async () => { - await runNativeCli([ - 'new', - 'oversized-spec-set', - ...(await creationArgs('oversized-spec-set')), - ...projectArgs(), - ]); + await runNativeCli(['new', 'oversized-spec-set', ...projectArgs()]); const paths = await nativeProjectPaths(projectRoot, 'docs'); const specsDir = path.join(paths.changesDir, 'oversized-spec-set', 'specs'); const fileBytes = NATIVE_CONTRACT_FILE_LIMITS.maxFileBytes - 1024; @@ -1306,9 +1214,7 @@ Pass. ] as const)( 'returns exit 70 with a retryable state when %s hits an unexpected filesystem failure', async (_command, args, retryCreatesChange) => { - const commandArgs = retryCreatesChange - ? [...args, ...(await creationArgs('storage-failure'))] - : [...args]; + const commandArgs = [...args]; const failure = Object.assign(new Error('simulated storage failure'), { code: 'EIO' }); const realpath = vi.spyOn(fs, 'realpath').mockRejectedValueOnce(failure); try { diff --git a/test/domains/comet-native/native-config.test.ts b/test/domains/comet-native/native-config.test.ts index 466c1d6b..f114e36b 100644 --- a/test/domains/comet-native/native-config.test.ts +++ b/test/domains/comet-native/native-config.test.ts @@ -167,7 +167,7 @@ describe('Native project configuration', () => { ).resolves.toContain('artifact_layout: docs'); }); - it('normalizes a missing Classic layout to legacy without changing the schema version', async () => { + it('normalizes a missing Classic layout to docs without changing the schema version', async () => { await fs.writeFile( path.join(projectRoot, '.comet', 'config.yaml'), [ @@ -183,7 +183,7 @@ describe('Native project configuration', () => { ); const value = await readProjectConfig(projectRoot); - expect(value?.classic?.artifact_layout).toBe('legacy'); + expect(value?.classic?.artifact_layout).toBe('docs'); expect(value?.schema).toBe('comet.project.v1'); }); diff --git a/test/domains/comet-native/native-evidence-transitions.test.ts b/test/domains/comet-native/native-evidence-transitions.test.ts index ebf794bb..cd6f809c 100644 --- a/test/domains/comet-native/native-evidence-transitions.test.ts +++ b/test/domains/comet-native/native-evidence-transitions.test.ts @@ -526,7 +526,7 @@ describe('Native evidence-bound phase transitions', () => { }), }, }), - ).resolves.toMatchObject({ change: { phase: 'archive' } }); + ).rejects.toThrow('signed acceptance-applicability review receipt'); }); it('redacts credential-shaped transition and partial-allowance text before hashing or persistence', async () => { diff --git a/test/domains/comet-native/native-review-trust.test.ts b/test/domains/comet-native/native-review-trust.test.ts index 4f638208..aac5511b 100644 --- a/test/domains/comet-native/native-review-trust.test.ts +++ b/test/domains/comet-native/native-review-trust.test.ts @@ -48,10 +48,7 @@ import type { NativeChangeState, NativeProjectPaths, } from '../../../domains/comet-native/native-types.js'; -import { - authorizeNativeTestChange, - installNativeControllerTrust, -} from '../../helpers/native-controller-trust.js'; +import { installNativeControllerTrust } from '../../helpers/native-controller-trust.js'; const brief = `# Outcome Ship trusted review. @@ -104,12 +101,6 @@ describe('Native pre-trusted review policy', () => { paths, name: 'trusted-review', language: 'en', - creationAuthorization: await authorizeNativeTestChange({ - projectRoot: root, - controller, - policy, - name: 'trusted-review', - }), }); await fs.writeFile(path.join(nativeChangeDir(paths, state.name), 'brief.md'), brief); await advanceNativeChange({ @@ -724,12 +715,6 @@ ${serializeNativeVerificationMachineBlock( paths: gitPaths, name: 'git-fence', language: 'en', - creationAuthorization: await authorizeNativeTestChange({ - projectRoot: gitRoot, - controller, - policy, - name: 'git-fence', - }), }); await fs.writeFile(path.join(nativeChangeDir(gitPaths, opened.name), 'brief.md'), brief); await advanceNativeChange({ diff --git a/test/domains/comet-native/native-skill.test.ts b/test/domains/comet-native/native-skill.test.ts index 7682eb6a..4d3fcf34 100644 --- a/test/domains/comet-native/native-skill.test.ts +++ b/test/domains/comet-native/native-skill.test.ts @@ -253,7 +253,7 @@ describe('Comet Native Skills', () => { ]) { expect(commands, `${language}: ${command}`).toContain(command); } - expect(commands).toContain('--creation-authorization '); + expect(commands).not.toContain('--creation-authorization'); expect(commands).toContain('--allow-partial-scope '); expect(commands).toContain('--independent-review-receipt '); expect(commands).toContain('--expect-preflight [--confirmed]'); @@ -285,8 +285,6 @@ describe('Comet Native Skills', () => { expect(enSkill).toContain('impersonate an external approval role'); expect(zhCommands).toContain('不得执行外部角色的 approve/sign'); expect(enCommands).toContain("must not perform an external role's approve or sign action"); - expect(zhCommands).toContain('等待 owner'); - expect(enCommands).toContain('wait for the owner'); }); it('uses recovery as an actionable runbook rather than a Runtime design document', async () => { diff --git a/test/domains/comet-native/native-verification-runtime.test.ts b/test/domains/comet-native/native-verification-runtime.test.ts index cb010107..7561d615 100644 --- a/test/domains/comet-native/native-verification-runtime.test.ts +++ b/test/domains/comet-native/native-verification-runtime.test.ts @@ -55,10 +55,7 @@ import { buildNativeReviewTrustPolicy, NATIVE_REVIEW_TRUST_POLICY_REF, } from '../../../domains/comet-native/native-review-trust.js'; -import { - authorizeNativeTestChange, - installNativeControllerTrust, -} from '../../helpers/native-controller-trust.js'; +import { installNativeControllerTrust } from '../../helpers/native-controller-trust.js'; const brief = `# Outcome Ship the focused behavior. @@ -124,13 +121,6 @@ describe('Native verification evidence runtime', () => { name: 'verified-change', language: 'en', now: new Date('2026-07-17T00:00:00.000Z'), - creationAuthorization: await authorizeNativeTestChange({ - projectRoot, - controller, - policy, - name: 'verified-change', - now: new Date('2026-07-17T00:00:00.000Z'), - }), }); changeDir = nativeChangeDir(paths, created.name); await fs.writeFile(path.join(changeDir, 'brief.md'), brief); diff --git a/test/domains/skill/classic-layout-skill-contract.test.ts b/test/domains/skill/classic-layout-skill-contract.test.ts index 452e7043..371e24b5 100644 --- a/test/domains/skill/classic-layout-skill-contract.test.ts +++ b/test/domains/skill/classic-layout-skill-contract.test.ts @@ -20,7 +20,7 @@ const LANGUAGE_CASES = [ ruleFiles: ['comet-phase-guard.md', 'comet-workflow-guard.md'], allowedLayoutDescriptionLines: new Set([ '- 新 Classic 项目默认使用 `docs/openspec/`。', - '- 缺少 `classic.artifact_layout` 的旧项目按 `openspec/` 继续运行。', + '- 缺少 `classic.artifact_layout` 时默认使用 `docs/openspec/`;`comet update` 检测到已有根目录 `openspec/` 产物时会显式补为 `legacy`,不会移动产物。', ]), }, { @@ -29,7 +29,7 @@ const LANGUAGE_CASES = [ ruleFiles: ['comet-phase-guard.en.md', 'comet-workflow-guard.en.md'], allowedLayoutDescriptionLines: new Set([ '- New Classic projects default to `docs/openspec/`.', - '- Existing projects without `classic.artifact_layout` continue to use `openspec/`.', + '- A missing `classic.artifact_layout` defaults to `docs/openspec/`. When `comet update` detects existing root-level `openspec/` artifacts, it explicitly backfills `legacy` without moving them.', ]), }, ] as const; diff --git a/test/domains/skill/skills.test.ts b/test/domains/skill/skills.test.ts index 571746f8..8a67a672 100644 --- a/test/domains/skill/skills.test.ts +++ b/test/domains/skill/skills.test.ts @@ -3100,7 +3100,7 @@ describe('skills', () => { expect(parse(content)).toMatchObject({ ambient_resume: true, classic: { - artifact_layout: 'legacy', + artifact_layout: 'docs', language: 'en', context_compression: 'off', review_mode: 'standard', @@ -3111,7 +3111,7 @@ describe('skills', () => { expect(content).not.toMatch(/^(language|context_compression|review_mode|auto_transition):/mu); }); - it('adds the sequential clarification default only to an existing Native block', async () => { + it('adds every managed Native default only to an existing Native block', async () => { const configDir = path.join(tmpDir, '.comet'); const configPath = path.join(configDir, 'config.yaml'); await fs.mkdir(configDir, { recursive: true }); @@ -3135,6 +3135,8 @@ describe('skills', () => { artifact_root: 'docs', language: 'en', clarification_mode: 'sequential', + archive_confirmation: 'automatic', + max_verify_failures: 5, snapshot: { include: ['**/*'], exclude: [], @@ -3205,7 +3207,7 @@ describe('skills', () => { const content = await fs.readFile(path.join(configDir, 'config.yaml'), 'utf-8'); expect(parse(content)).toMatchObject({ classic: { - artifact_layout: 'legacy', + artifact_layout: 'docs', language: 'en', context_compression: 'beta', review_mode: 'standard', @@ -3339,7 +3341,7 @@ describe('skills', () => { expect(parse(second)).toMatchObject({ classic: { - artifact_layout: 'legacy', + artifact_layout: 'docs', language: 'zh-CN', context_compression: 'beta', review_mode: 'thorough', diff --git a/test/domains/workflow-contract/workflow-contract.test.ts b/test/domains/workflow-contract/workflow-contract.test.ts index 78830113..373379ec 100644 --- a/test/domains/workflow-contract/workflow-contract.test.ts +++ b/test/domains/workflow-contract/workflow-contract.test.ts @@ -211,7 +211,7 @@ describe('workflow contract normalization', () => { expect(() => normalizeWorkflowArtifactRoot('docs/')).toThrow( 'native.artifact_root must not contain empty or dot path segments', ); - expect(normalizeClassicArtifactLayout(undefined)).toBe('legacy'); + expect(normalizeClassicArtifactLayout(undefined)).toBe('docs'); expect(() => normalizeClassicArtifactLayout('elsewhere')).toThrow( 'classic.artifact_layout must be legacy or docs', ); diff --git a/test/helpers/native-controller-trust.ts b/test/helpers/native-controller-trust.ts index 5ae8c20f..6dd83e86 100644 --- a/test/helpers/native-controller-trust.ts +++ b/test/helpers/native-controller-trust.ts @@ -2,23 +2,17 @@ import { promises as fs } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { - buildNativeCreationAuthorization, - type NativeCreationAuthorization, -} from '../../domains/comet-native/native-creation-authorization.js'; import { buildNativeControllerTrustStore, nativeControllerProjectRootHash, NATIVE_CONTROLLER_TRUST_STORE_TEST_ENV, } from '../../domains/comet-native/native-controller-trust.js'; import type { NativeReviewKeyPair } from '../../domains/comet-native/native-review-identity.js'; -import type { NativeReviewTrustPolicy } from '../../domains/comet-native/native-review-trust.js'; import { registerTrustedReadonlyFileForTest } from '../../platform/fs/trusted-readonly-file.js'; export async function installNativeControllerTrust(options: { projectRoot: string; controller: NativeReviewKeyPair; - legacyChanges?: readonly string[]; }): Promise<() => Promise> { const storeRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'comet-native-controller-trust-')); const storePath = path.join(storeRoot, 'controller-trust.json'); @@ -27,9 +21,6 @@ export async function installNativeControllerTrust(options: { { projectRootHash, controllerIdentity: options.controller.identity, - legacyChanges: [...(options.legacyChanges ?? [])].sort((left, right) => - left.localeCompare(right, 'en'), - ), }, ]); await fs.writeFile(storePath, `${JSON.stringify(store, null, 2)}\n`); @@ -46,20 +37,3 @@ export async function installNativeControllerTrust(options: { await fs.rm(storeRoot, { recursive: true, force: true }); }; } - -export async function authorizeNativeTestChange(options: { - projectRoot: string; - controller: NativeReviewKeyPair; - policy: NativeReviewTrustPolicy; - name: string; - now?: Date; -}): Promise { - return buildNativeCreationAuthorization({ - controllerIdentity: options.controller.identity, - controllerPrivateKey: options.controller.privateKey, - projectRootHash: await nativeControllerProjectRootHash(options.projectRoot), - policyHash: options.policy.policyHash, - change: options.name, - now: options.now, - }); -} diff --git a/test/repository/native-runtime-assets.test.ts b/test/repository/native-runtime-assets.test.ts index bc46dd13..34c11d2f 100644 --- a/test/repository/native-runtime-assets.test.ts +++ b/test/repository/native-runtime-assets.test.ts @@ -71,14 +71,10 @@ describe('Native runtime release asset', () => { expect(source).toContain('parseCometHookRequest'); expect(source).toContain('Hook write target could not be determined'); expect(source).toContain('comet.native.controller-trust-store.v1'); - expect(source).toContain('comet.native.creation-authorization.v1'); + expect(source).not.toContain('comet.native.creation-authorization.v1'); expect(source).toContain('comet.native.review-trust-policy.v2'); - expect(source).toContain( - 'trust authorize --controller-private-key-env --output ', - ); - expect(source).toContain( - 'new --creation-authorization [--language en|zh-CN]', - ); + expect(source).not.toContain('trust authorize'); + expect(source).toContain('new [--language en|zh-CN]'); execFileSync(process.execPath, [builder, '--check'], { stdio: 'pipe' }); }); @@ -92,12 +88,8 @@ describe('Native runtime release asset', () => { 'utf8', ); - expect(english).toContain( - 'comet native new --creation-authorization [--language en|zh-CN]', - ); - expect(chinese).toContain( - 'comet native new --creation-authorization [--language en|zh-CN]', - ); + expect(english).toContain('comet native new [--language en|zh-CN]'); + expect(chinese).toContain('comet native new [--language en|zh-CN]'); expect(english).toContain('`new` creates default configuration and `/docs/comet/`'); expect(chinese).toContain('`new` 在配置缺失时创建默认配置和 `/docs/comet/`'); }); diff --git a/test/repository/release-metadata.test.ts b/test/repository/release-metadata.test.ts index a4f9476a..79523f63 100644 --- a/test/repository/release-metadata.test.ts +++ b/test/repository/release-metadata.test.ts @@ -16,7 +16,7 @@ describe('release metadata', () => { readFileSync(path.join(repositoryRoot, 'assets', 'manifest.json'), 'utf8'), ) as { version: string }; - expect(packageJson.version).toBe('0.4.0-beta.10'); + expect(packageJson.version).toBe('0.4.0-beta.11'); expect(packageLock.version).toBe(packageJson.version); expect(packageLock.packages[''].version).toBe(packageJson.version); expect(assetsManifest.version).toBe(packageJson.version); diff --git a/website b/website index 2971e8ff..f2725bd1 160000 --- a/website +++ b/website @@ -1 +1 @@ -Subproject commit 2971e8ffbd41cc01e48cfbd51e5d05b80db98fbb +Subproject commit f2725bd1d1cead8b091f10494b026319c35cb2ae