release-skill 0.9.15 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +10 -0
- package/CHANGELOG.md +61 -0
- package/INSTALL.md +57 -9
- package/INSTALL.zh-CN.md +44 -7
- package/README.md +88 -29
- package/README.zh-CN.md +70 -27
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +1471 -672
- package/adapters/claude/schemas/release-plan.schema.json +1 -1
- package/adapters/claude/schemas/release-project.schema.json +19 -1
- package/adapters/claude/skills/release-finish/SKILL.md +65 -6
- package/adapters/claude/skills/release-help/SKILL.md +3 -3
- package/adapters/claude/skills/release-verify/SKILL.md +2 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +1471 -672
- package/adapters/codex/schemas/release-plan.schema.json +1 -1
- package/adapters/codex/schemas/release-project.schema.json +19 -1
- package/adapters/codex/skills/release-finish/SKILL.md +65 -6
- package/adapters/codex/skills/release-help/SKILL.md +3 -3
- package/adapters/codex/skills/release-verify/SKILL.md +2 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +1471 -672
- package/adapters/kimi/schemas/release-plan.schema.json +1 -1
- package/adapters/kimi/schemas/release-project.schema.json +19 -1
- package/adapters/kimi/skills/release-finish/SKILL.md +65 -6
- package/adapters/kimi/skills/release-help/SKILL.md +3 -3
- package/adapters/kimi/skills/release-verify/SKILL.md +2 -2
- package/adapters/qoder/.qoder-plugin/plugin.json +10 -0
- package/adapters/qoder/bin/consumer-contract-vectors.json +110 -0
- package/adapters/qoder/bin/error-codes.json +161 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-contracts/LICENSE +202 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-contracts/NOTICE +10 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/LICENSE +202 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/NOTICE +10 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-engineering-kit/THIRD_PARTY_NOTICES +142 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/LICENSE +202 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/NOTICE +10 -0
- package/adapters/qoder/bin/foundation-legal/skill-family-harness-node/native-NOTICE +2 -0
- package/adapters/qoder/bin/foundation-resource-binding.json +1 -0
- package/adapters/qoder/bin/kernel-protocol.json +60 -0
- package/adapters/qoder/bin/license-texts/Apache-2.0.txt +201 -0
- package/adapters/qoder/bin/license-texts/MIT.txt +21 -0
- package/adapters/qoder/bin/prebuild-manifest.json +79 -0
- package/adapters/qoder/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/qoder/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/qoder/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/qoder/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/qoder/bin/registry.json +347 -0
- package/adapters/qoder/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/qoder/bin/release-skill.bundle.mjs +149446 -0
- package/adapters/qoder/bin/release-skill.mjs +54 -0
- package/adapters/qoder/bin/rules.json +104 -0
- package/adapters/qoder/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/qoder/data/hosts/claude/host-descriptor.json +45 -0
- package/adapters/qoder/data/hosts/codebuddy/host-descriptor.json +26 -0
- package/adapters/qoder/data/hosts/codex/host-descriptor.json +45 -0
- package/adapters/qoder/data/hosts/deepseek-harness/host-descriptor.json +23 -0
- package/adapters/qoder/data/hosts/kimi-code/host-descriptor.json +36 -0
- package/adapters/qoder/data/hosts/qoder/host-descriptor.json +26 -0
- package/adapters/qoder/data/hosts/registry.json +13 -0
- package/adapters/qoder/data/hosts/workbuddy/host-descriptor.json +34 -0
- package/adapters/qoder/data/licensing/registry.json +211 -0
- package/adapters/qoder/data/licensing/schema.json +207 -0
- package/adapters/qoder/native/safe-write/binding.gyp +41 -0
- package/adapters/qoder/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
- package/adapters/qoder/native/safe-write/prebuilds.json +24 -0
- package/adapters/qoder/native/safe-write/src/safe_write.cc +2032 -0
- package/adapters/qoder/schemas/.render-manifest.json +41 -0
- package/adapters/qoder/schemas/approval-record.schema.json +115 -0
- package/adapters/qoder/schemas/artifact-lock.schema.json +111 -0
- package/adapters/qoder/schemas/artifact-plan.schema.json +52 -0
- package/adapters/qoder/schemas/artifact-policy.schema.json +76 -0
- package/adapters/qoder/schemas/evidence-event-v2.schema.json +164 -0
- package/adapters/qoder/schemas/evidence-event.schema.json +89 -0
- package/adapters/qoder/schemas/postpublish-approval-record.schema.json +47 -0
- package/adapters/qoder/schemas/release-plan.schema.json +1996 -0
- package/adapters/qoder/schemas/release-project.schema.json +1613 -0
- package/adapters/qoder/schemas/release-run.schema.json +777 -0
- package/adapters/qoder/skills/release-assess/SKILL.md +60 -0
- package/adapters/qoder/skills/release-config/SKILL.md +150 -0
- package/adapters/qoder/skills/release-docs/SKILL.md +119 -0
- package/adapters/qoder/skills/release-finish/SKILL.md +141 -0
- package/adapters/qoder/skills/release-help/SKILL.md +153 -0
- package/adapters/qoder/skills/release-marketplace/SKILL.md +156 -0
- package/adapters/qoder/skills/release-prepare/SKILL.md +135 -0
- package/adapters/qoder/skills/release-publish/SKILL.md +91 -0
- package/adapters/qoder/skills/release-reconcile/SKILL.md +80 -0
- package/adapters/qoder/skills/release-setup/SKILL.md +131 -0
- package/adapters/qoder/skills/release-verify/SKILL.md +109 -0
- package/adapters/qoder/src/schemas/adapter-build-manifest.schema.json +41 -0
- package/adapters/qoder/src/schemas/adapter-peer-verification-request.schema.json +54 -0
- package/adapters/qoder/src/schemas/adapter-peer-verification-result.schema.json +54 -0
- package/adapters/qoder/src/schemas/adapter-source.schema.json +47 -0
- package/adapters/qoder/src/schemas/audit-baseline-pin.schema.json +163 -0
- package/adapters/qoder/src/schemas/declared-read-surface-result.schema.json +61 -0
- package/adapters/qoder/src/schemas/engineering-baseline.schema.json +77 -0
- package/adapters/qoder/src/schemas/executable-identity-observation.schema.json +162 -0
- package/adapters/qoder/src/schemas/filesystem-root-binding.schema.json +14 -0
- package/adapters/qoder/src/schemas/filesystem-tree-observation.schema.json +71 -0
- package/adapters/qoder/src/schemas/fixed-set-publication-manifest.schema.json +108 -0
- package/adapters/qoder/src/schemas/fixed-set-publication-receipt.schema.json +148 -0
- package/adapters/qoder/src/schemas/host-capability-fact.schema.json +31 -0
- package/adapters/qoder/src/schemas/host-descriptor.schema.json +137 -0
- package/adapters/qoder/src/schemas/host-operation-plan.schema.json +57 -0
- package/adapters/qoder/src/schemas/host-operation-receipt.schema.json +81 -0
- package/adapters/qoder/src/schemas/host-probe-result.schema.json +30 -0
- package/adapters/qoder/src/schemas/host-registry.schema.json +18 -0
- package/adapters/qoder/src/schemas/host-verification-request.schema.json +67 -0
- package/adapters/qoder/src/schemas/host-verification-result.schema.json +149 -0
- package/adapters/qoder/src/schemas/managed-file-lock.schema.json +77 -0
- package/adapters/qoder/src/schemas/migration-manifest.schema.json +412 -0
- package/adapters/qoder/src/schemas/observation-scope.schema.json +91 -0
- package/adapters/qoder/src/schemas/operation-request.schema.json +58 -0
- package/adapters/qoder/src/schemas/operation-result.schema.json +130 -0
- package/adapters/qoder/src/schemas/platform-difference-registry.schema.json +110 -0
- package/adapters/qoder/src/schemas/plugin-verification-request.schema.json +499 -0
- package/adapters/qoder/src/schemas/plugin-verification-result.schema.json +1217 -0
- package/adapters/qoder/src/schemas/profile-adoption-declaration.schema.json +111 -0
- package/adapters/qoder/src/schemas/profile-descriptor.schema.json +99 -0
- package/adapters/qoder/src/schemas/project-manifest.schema.json +67 -0
- package/adapters/qoder/src/schemas/project-profile.schema.json +25 -0
- package/adapters/qoder/src/schemas/public-boundary-declaration.schema.json +73 -0
- package/adapters/qoder/src/schemas/report-binding.schema.json +42 -0
- package/adapters/qoder/src/schemas/report-model.schema.json +308 -0
- package/adapters/qoder/src/schemas/skill-family-directory-verification-request.schema.json +100 -0
- package/adapters/qoder/src/schemas/skill-family-directory-verification-result.schema.json +193 -0
- package/adapters/qoder/src/schemas/source-authority-receipt.schema.json +31 -0
- package/adapters/qoder/src/schemas/state-event-envelope.schema.json +38 -0
- package/adapters/qoder/src/schemas/state-snapshot-metadata.schema.json +25 -0
- package/adapters/qoder/src/schemas/structured-scan-policy.schema.json +97 -0
- package/adapters/qoder/src/schemas/surface-scan-policy.schema.json +42 -0
- package/adapters/qoder/src/schemas/timeout-policy.schema.json +24 -0
- package/adapters/qoder/src/schemas/token-estimate-record.schema.json +75 -0
- package/adapters/qoder/src/schemas/token-estimate-result.schema.json +43 -0
- package/adapters/qoder/src/schemas/watchdog-termination-envelope.schema.json +156 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +1471 -672
- package/adapters/workbuddy/schemas/release-plan.schema.json +1 -1
- package/adapters/workbuddy/schemas/release-project.schema.json +19 -1
- package/adapters/workbuddy/skills/release-finish/SKILL.md +65 -6
- package/adapters/workbuddy/skills/release-help/SKILL.md +3 -3
- package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -2
- package/bin/release-skill-cli.mjs +152 -6
- package/bin/release-skill.bundle.mjs +1471 -672
- package/package.json +4 -2
- package/platform-manifest.json +70 -4
- package/references/02-project-config.md +25 -2
- package/schemas/release-plan.schema.json +1 -1
- package/schemas/release-project.schema.json +19 -1
- package/skills/release-finish/SKILL.md +65 -6
- package/skills/release-help/SKILL.md +3 -3
- package/skills/release-verify/SKILL.md +2 -2
- package/skills-src/release-finish/SKILL.md +65 -6
- package/skills-src/release-help/SKILL.md +3 -3
- package/skills-src/release-verify/SKILL.md +2 -2
- package/src/commands/post-release-local.mjs +360 -51
- package/src/commands/verify-records.mjs +467 -0
- package/src/core/postpublish.mjs +1 -1
- package/src/core/run.mjs +1 -1
- package/src/producers/build-adapters.mjs +36 -13
|
@@ -9,9 +9,9 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
|
|
|
9
9
|
// Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
|
|
10
10
|
const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
|
|
11
11
|
// Package identity injected at build time — closure-independent --version probe.
|
|
12
|
-
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.
|
|
12
|
+
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.9.17"});
|
|
13
13
|
// Build-time source digest for the BUNDLE_STALE freshness gate (see above).
|
|
14
|
-
const __bundleSourceDigest = "
|
|
14
|
+
const __bundleSourceDigest = "8d2e6384871f9dee0d333c03e859296020ef69fc38efddb196fd7aecf76511d7";
|
|
15
15
|
|
|
16
16
|
var __create = Object.create;
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
@@ -36109,6 +36109,13 @@ var require_dist3 = __commonJS({
|
|
|
36109
36109
|
}
|
|
36110
36110
|
});
|
|
36111
36111
|
|
|
36112
|
+
// src/core/foundation-inflight.mjs
|
|
36113
|
+
var init_foundation_inflight = __esm({
|
|
36114
|
+
"src/core/foundation-inflight.mjs"() {
|
|
36115
|
+
init_src2();
|
|
36116
|
+
}
|
|
36117
|
+
});
|
|
36118
|
+
|
|
36112
36119
|
// src/core/digest.mjs
|
|
36113
36120
|
function canonicalJson2(obj) {
|
|
36114
36121
|
const normalized = normalizeLenient(obj);
|
|
@@ -36160,13 +36167,6 @@ var init_digest = __esm({
|
|
|
36160
36167
|
}
|
|
36161
36168
|
});
|
|
36162
36169
|
|
|
36163
|
-
// src/core/foundation-inflight.mjs
|
|
36164
|
-
var init_foundation_inflight = __esm({
|
|
36165
|
-
"src/core/foundation-inflight.mjs"() {
|
|
36166
|
-
init_src2();
|
|
36167
|
-
}
|
|
36168
|
-
});
|
|
36169
|
-
|
|
36170
36170
|
// src/core/pkg-root.mjs
|
|
36171
36171
|
import { dirname, resolve } from "node:path";
|
|
36172
36172
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -43519,604 +43519,11 @@ var init_plan = __esm({
|
|
|
43519
43519
|
}
|
|
43520
43520
|
});
|
|
43521
43521
|
|
|
43522
|
-
// src/core/run.mjs
|
|
43523
|
-
var run_exports = {};
|
|
43524
|
-
__export(run_exports, {
|
|
43525
|
-
appendRunState: () => appendRunState,
|
|
43526
|
-
assertImmutableRunAuthority: () => assertImmutableRunAuthority,
|
|
43527
|
-
computeRunDigest: () => computeRunDigest,
|
|
43528
|
-
createProductionPrepareRunDir: () => createProductionPrepareRunDir,
|
|
43529
|
-
createProductionRunDir: () => createProductionRunDir,
|
|
43530
|
-
loadRun: () => loadRun,
|
|
43531
|
-
resolveDefaultRunDir: () => resolveDefaultRunDir,
|
|
43532
|
-
resolveRunPath: () => resolveRunPath,
|
|
43533
|
-
validateRun: () => validateRun,
|
|
43534
|
-
validateRunCheckpointMapping: () => validateRunCheckpointMapping,
|
|
43535
|
-
validateRunLineage: () => validateRunLineage,
|
|
43536
|
-
validateRunPlanDigest: () => validateRunPlanDigest,
|
|
43537
|
-
writeRunAtomic: () => writeRunAtomic
|
|
43538
|
-
});
|
|
43539
|
-
import { lstat as lstat27, mkdir as mkdir10, readFile as readFile18, stat as stat10 } from "node:fs/promises";
|
|
43540
|
-
import { realpathSync as realpathSync3 } from "node:fs";
|
|
43541
|
-
import { dirname as dirname4, join as join7, resolve as resolve8, basename as basename4, relative as relative7, isAbsolute as isAbsolute5 } from "node:path";
|
|
43542
|
-
function resolveDefaultRunDir(planPath, command2, runId = `${command2}-${Date.now()}`) {
|
|
43543
|
-
const absolute2 = resolve8(planPath);
|
|
43544
|
-
const fileName = basename4(absolute2);
|
|
43545
|
-
const parentDir = dirname4(absolute2);
|
|
43546
|
-
if (fileName === "release-plan.json") {
|
|
43547
|
-
return `${parentDir}/runs/${runId}`;
|
|
43548
|
-
}
|
|
43549
|
-
if (basename4(parentDir) === "plans") {
|
|
43550
|
-
const releaseDir = dirname4(parentDir);
|
|
43551
|
-
return join7(releaseDir, "runs", runId);
|
|
43552
|
-
}
|
|
43553
|
-
return `${parentDir}/runs/${runId}`;
|
|
43554
|
-
}
|
|
43555
|
-
function validateRun(run6, options = {}) {
|
|
43556
|
-
const valid = validateRunSchema(run6);
|
|
43557
|
-
if (!valid) {
|
|
43558
|
-
const errors = validateRunSchema.errors ?? [];
|
|
43559
|
-
const summary = errors.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
|
|
43560
|
-
throw new ReleaseError(
|
|
43561
|
-
GATE_FAILED,
|
|
43562
|
-
`release run schema validation failed: ${summary}`,
|
|
43563
|
-
{ validationErrors: errors }
|
|
43564
|
-
);
|
|
43565
|
-
}
|
|
43566
|
-
const actionIds = /* @__PURE__ */ new Set();
|
|
43567
|
-
for (const checkpoint of run6.checkpoints ?? []) {
|
|
43568
|
-
if (actionIds.has(checkpoint.actionId)) {
|
|
43569
|
-
throw new ReleaseError(
|
|
43570
|
-
GATE_FAILED,
|
|
43571
|
-
`release run has duplicate checkpoint for action "${checkpoint.actionId}"`,
|
|
43572
|
-
{ actionId: checkpoint.actionId }
|
|
43573
|
-
);
|
|
43574
|
-
}
|
|
43575
|
-
actionIds.add(checkpoint.actionId);
|
|
43576
|
-
}
|
|
43577
|
-
if (options.requireDigest === true && !run6.runDigest) {
|
|
43578
|
-
throw new ReleaseError(GATE_FAILED, "release run is missing required runDigest");
|
|
43579
|
-
}
|
|
43580
|
-
if (run6.runDigest && run6.runDigest !== computeRunDigest(run6)) {
|
|
43581
|
-
throw new ReleaseError(
|
|
43582
|
-
GATE_FAILED,
|
|
43583
|
-
"release run digest does not match its content",
|
|
43584
|
-
{ expected: computeRunDigest(run6), actual: run6.runDigest }
|
|
43585
|
-
);
|
|
43586
|
-
}
|
|
43587
|
-
}
|
|
43588
|
-
async function resolveRunPath(runPath) {
|
|
43589
|
-
let stats;
|
|
43590
|
-
try {
|
|
43591
|
-
stats = await lstat27(runPath);
|
|
43592
|
-
} catch (err) {
|
|
43593
|
-
if (err?.code === "ENOENT") return runPath;
|
|
43594
|
-
throw new ReleaseError(
|
|
43595
|
-
GATE_FAILED,
|
|
43596
|
-
`cannot inspect release run path: ${err.message}`,
|
|
43597
|
-
{ runPath, cause: err.code }
|
|
43598
|
-
);
|
|
43599
|
-
}
|
|
43600
|
-
if (stats.isFile()) return runPath;
|
|
43601
|
-
if (stats.isSymbolicLink()) {
|
|
43602
|
-
let targetStats;
|
|
43603
|
-
try {
|
|
43604
|
-
targetStats = await stat10(runPath);
|
|
43605
|
-
} catch (err) {
|
|
43606
|
-
if (err?.code === "ENOENT") return runPath;
|
|
43607
|
-
throw new ReleaseError(
|
|
43608
|
-
GATE_FAILED,
|
|
43609
|
-
`cannot inspect release run path: ${err.message}`,
|
|
43610
|
-
{ runPath, cause: err.code }
|
|
43611
|
-
);
|
|
43612
|
-
}
|
|
43613
|
-
if (targetStats.isDirectory()) {
|
|
43614
|
-
throw new ReleaseError(
|
|
43615
|
-
GATE_FAILED,
|
|
43616
|
-
"release run path is a symlinked directory; pass the run file (release-run.json) or the real run directory",
|
|
43617
|
-
{ runPath }
|
|
43618
|
-
);
|
|
43619
|
-
}
|
|
43620
|
-
return runPath;
|
|
43621
|
-
}
|
|
43622
|
-
if (stats.isDirectory()) {
|
|
43623
|
-
const candidate = join7(runPath, "release-run.json");
|
|
43624
|
-
try {
|
|
43625
|
-
const candidateStats = await lstat27(candidate);
|
|
43626
|
-
if (!candidateStats.isFile()) {
|
|
43627
|
-
throw new ReleaseError(
|
|
43628
|
-
GATE_FAILED,
|
|
43629
|
-
`release run directory does not contain a regular release-run.json: ${runPath}`,
|
|
43630
|
-
{ runPath, expected: "release-run.json" }
|
|
43631
|
-
);
|
|
43632
|
-
}
|
|
43633
|
-
} catch (err) {
|
|
43634
|
-
if (err instanceof ReleaseError) throw err;
|
|
43635
|
-
throw new ReleaseError(
|
|
43636
|
-
GATE_FAILED,
|
|
43637
|
-
`release run directory must contain release-run.json: ${err.message}`,
|
|
43638
|
-
{ runPath, expected: "release-run.json", cause: err.code }
|
|
43639
|
-
);
|
|
43640
|
-
}
|
|
43641
|
-
return candidate;
|
|
43642
|
-
}
|
|
43643
|
-
return runPath;
|
|
43644
|
-
}
|
|
43645
|
-
async function loadRun(runPath, options = {}) {
|
|
43646
|
-
const resolvedRunPath = await resolveRunPath(runPath);
|
|
43647
|
-
let raw;
|
|
43648
|
-
try {
|
|
43649
|
-
raw = await readFile18(resolvedRunPath, "utf8");
|
|
43650
|
-
} catch (err) {
|
|
43651
|
-
throw new ReleaseError(
|
|
43652
|
-
GATE_FAILED,
|
|
43653
|
-
`cannot read release run: ${err.message}`,
|
|
43654
|
-
{ runPath: resolvedRunPath, cause: err.code }
|
|
43655
|
-
);
|
|
43656
|
-
}
|
|
43657
|
-
let run6;
|
|
43658
|
-
try {
|
|
43659
|
-
run6 = JSON.parse(raw);
|
|
43660
|
-
} catch (err) {
|
|
43661
|
-
throw new ReleaseError(
|
|
43662
|
-
GATE_FAILED,
|
|
43663
|
-
`release run is not valid JSON: ${err.message}`,
|
|
43664
|
-
{ runPath: resolvedRunPath }
|
|
43665
|
-
);
|
|
43666
|
-
}
|
|
43667
|
-
validateRun(run6, options);
|
|
43668
|
-
if (options.authorityPlanPath) {
|
|
43669
|
-
assertImmutableRunAuthority(resolvedRunPath, options.authorityPlanPath, run6);
|
|
43670
|
-
}
|
|
43671
|
-
return run6;
|
|
43672
|
-
}
|
|
43673
|
-
function assertImmutableRunAuthority(runPath, planPath, run6) {
|
|
43674
|
-
const absolutePlan = realpathSync3(resolve8(planPath));
|
|
43675
|
-
const planDir = dirname4(absolutePlan);
|
|
43676
|
-
if (basename4(planDir) !== "plans") {
|
|
43677
|
-
throw new ReleaseError(GATE_FAILED, "run authority requires an immutable plans/<digest>.json plan path");
|
|
43678
|
-
}
|
|
43679
|
-
let authorityRoot = dirname4(planDir);
|
|
43680
|
-
let cursor = planDir;
|
|
43681
|
-
while (dirname4(cursor) !== cursor) {
|
|
43682
|
-
if (basename4(cursor) === ".release-skill") {
|
|
43683
|
-
authorityRoot = cursor;
|
|
43684
|
-
break;
|
|
43685
|
-
}
|
|
43686
|
-
cursor = dirname4(cursor);
|
|
43687
|
-
}
|
|
43688
|
-
const runsDir = join7(authorityRoot, "runs");
|
|
43689
|
-
const absoluteRun = realpathSync3(resolve8(runPath));
|
|
43690
|
-
const rel = relative7(runsDir, absoluteRun);
|
|
43691
|
-
if (isAbsolute5(rel) || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
43692
|
-
throw new ReleaseError(GATE_FAILED, "production run authority must be inside the plan sibling runs/ directory");
|
|
43693
|
-
}
|
|
43694
|
-
const fileName = basename4(absoluteRun);
|
|
43695
|
-
const isFinal = fileName === "release-run.json";
|
|
43696
|
-
const isState = /^\d{6}\.json$/.test(fileName) && basename4(dirname4(absoluteRun)) === "states" && run6.stateSequence === Number(fileName.slice(0, 6));
|
|
43697
|
-
if (!isFinal && !isState) {
|
|
43698
|
-
throw new ReleaseError(
|
|
43699
|
-
GATE_FAILED,
|
|
43700
|
-
"production run authority must be release-run.json or a bound states/<sequence>.json snapshot",
|
|
43701
|
-
{ runPath }
|
|
43702
|
-
);
|
|
43703
|
-
}
|
|
43704
|
-
}
|
|
43705
|
-
async function createProductionRunDir(runDir, planPath) {
|
|
43706
|
-
const absolutePlan = realpathSync3(resolve8(planPath));
|
|
43707
|
-
const planDir = dirname4(absolutePlan);
|
|
43708
|
-
if (basename4(planDir) !== "plans") {
|
|
43709
|
-
throw new ReleaseError(GATE_FAILED, "production run directory requires an immutable plans/<digest>.json authority");
|
|
43710
|
-
}
|
|
43711
|
-
const authorityRoot = dirname4(planDir);
|
|
43712
|
-
return createProductionRunDirWithinAuthority(runDir, authorityRoot);
|
|
43713
|
-
}
|
|
43714
|
-
async function createProductionPrepareRunDir(runDir, releaseDir) {
|
|
43715
|
-
const authorityRoot = resolve8(releaseDir);
|
|
43716
|
-
let authorityStat;
|
|
43717
|
-
try {
|
|
43718
|
-
authorityStat = await lstat27(authorityRoot);
|
|
43719
|
-
} catch (error) {
|
|
43720
|
-
throw new ReleaseError(GATE_FAILED, "cannot inspect production .release-skill authority root", {
|
|
43721
|
-
releaseDir: authorityRoot,
|
|
43722
|
-
cause: error.code
|
|
43723
|
-
});
|
|
43724
|
-
}
|
|
43725
|
-
if (authorityStat.isSymbolicLink() || !authorityStat.isDirectory()) {
|
|
43726
|
-
throw new ReleaseError(
|
|
43727
|
-
GATE_FAILED,
|
|
43728
|
-
"production .release-skill authority root must be a real directory, not a symlink or special file",
|
|
43729
|
-
{ releaseDir: authorityRoot }
|
|
43730
|
-
);
|
|
43731
|
-
}
|
|
43732
|
-
const physicalAuthorityRoot = realpathSync3(authorityRoot);
|
|
43733
|
-
if (physicalAuthorityRoot !== authorityRoot) {
|
|
43734
|
-
throw new ReleaseError(
|
|
43735
|
-
GATE_FAILED,
|
|
43736
|
-
"production .release-skill authority root physical identity does not match the project authority",
|
|
43737
|
-
{ releaseDir: authorityRoot, physicalReleaseDir: physicalAuthorityRoot }
|
|
43738
|
-
);
|
|
43739
|
-
}
|
|
43740
|
-
return createProductionRunDirWithinAuthority(runDir, physicalAuthorityRoot);
|
|
43741
|
-
}
|
|
43742
|
-
async function createProductionRunDirWithinAuthority(runDir, authorityRoot) {
|
|
43743
|
-
const runsDir = join7(authorityRoot, "runs");
|
|
43744
|
-
let runsStat;
|
|
43745
|
-
try {
|
|
43746
|
-
runsStat = await lstat27(runsDir);
|
|
43747
|
-
} catch (error) {
|
|
43748
|
-
if (error.code !== "ENOENT") {
|
|
43749
|
-
throw new ReleaseError(GATE_FAILED, "cannot inspect production runs authority root", {
|
|
43750
|
-
runsDir,
|
|
43751
|
-
cause: error.code
|
|
43752
|
-
});
|
|
43753
|
-
}
|
|
43754
|
-
try {
|
|
43755
|
-
await mkdir10(runsDir, { mode: 448 });
|
|
43756
|
-
} catch (mkdirError) {
|
|
43757
|
-
if (mkdirError.code !== "EEXIST") {
|
|
43758
|
-
throw new ReleaseError(GATE_FAILED, "cannot create production runs authority root", {
|
|
43759
|
-
runsDir,
|
|
43760
|
-
cause: mkdirError.code
|
|
43761
|
-
});
|
|
43762
|
-
}
|
|
43763
|
-
}
|
|
43764
|
-
runsStat = await lstat27(runsDir);
|
|
43765
|
-
}
|
|
43766
|
-
if (runsStat.isSymbolicLink() || !runsStat.isDirectory()) {
|
|
43767
|
-
throw new ReleaseError(
|
|
43768
|
-
GATE_FAILED,
|
|
43769
|
-
"production .release-skill/runs authority root must be a real directory, not a symlink or special file",
|
|
43770
|
-
{ runsDir }
|
|
43771
|
-
);
|
|
43772
|
-
}
|
|
43773
|
-
const physicalRunsDir = realpathSync3(runsDir);
|
|
43774
|
-
if (physicalRunsDir !== runsDir) {
|
|
43775
|
-
throw new ReleaseError(
|
|
43776
|
-
GATE_FAILED,
|
|
43777
|
-
"production .release-skill/runs authority root physical identity does not match the plan authority",
|
|
43778
|
-
{ runsDir, physicalRunsDir }
|
|
43779
|
-
);
|
|
43780
|
-
}
|
|
43781
|
-
const requested = resolve8(runDir);
|
|
43782
|
-
let physicalParent;
|
|
43783
|
-
try {
|
|
43784
|
-
physicalParent = realpathSync3(dirname4(requested));
|
|
43785
|
-
} catch (error) {
|
|
43786
|
-
throw new ReleaseError(GATE_FAILED, "production run directory parent is unavailable", {
|
|
43787
|
-
runDir,
|
|
43788
|
-
cause: error.code
|
|
43789
|
-
});
|
|
43790
|
-
}
|
|
43791
|
-
if (physicalParent !== physicalRunsDir || basename4(requested) === "" || basename4(requested) === "." || basename4(requested) === "..") {
|
|
43792
|
-
throw new ReleaseError(
|
|
43793
|
-
GATE_FAILED,
|
|
43794
|
-
"production run directory must be a fresh direct child of the immutable .release-skill/runs authority",
|
|
43795
|
-
{ runDir, runsDir: physicalRunsDir }
|
|
43796
|
-
);
|
|
43797
|
-
}
|
|
43798
|
-
try {
|
|
43799
|
-
await lstat27(requested);
|
|
43800
|
-
throw new ReleaseError(GATE_FAILED, "production run directory already exists; authority directories cannot be reused", { runDir });
|
|
43801
|
-
} catch (error) {
|
|
43802
|
-
if (error instanceof ReleaseError) throw error;
|
|
43803
|
-
if (error.code !== "ENOENT") {
|
|
43804
|
-
throw new ReleaseError(GATE_FAILED, "cannot establish fresh production run directory", {
|
|
43805
|
-
runDir,
|
|
43806
|
-
cause: error.code
|
|
43807
|
-
});
|
|
43808
|
-
}
|
|
43809
|
-
}
|
|
43810
|
-
try {
|
|
43811
|
-
await mkdir10(requested, { mode: 448 });
|
|
43812
|
-
} catch (error) {
|
|
43813
|
-
throw new ReleaseError(GATE_FAILED, "cannot exclusively create production run directory", {
|
|
43814
|
-
runDir,
|
|
43815
|
-
cause: error.code
|
|
43816
|
-
});
|
|
43817
|
-
}
|
|
43818
|
-
const physicalRunDir = realpathSync3(requested);
|
|
43819
|
-
if (dirname4(physicalRunDir) !== physicalRunsDir) {
|
|
43820
|
-
throw new ReleaseError(GATE_FAILED, "production run directory escaped its immutable authority after creation", { runDir });
|
|
43821
|
-
}
|
|
43822
|
-
return physicalRunDir;
|
|
43823
|
-
}
|
|
43824
|
-
async function validateStatePredecessorChain(run6, runPath, options = {}) {
|
|
43825
|
-
if (run6.stateSequence === void 0) {
|
|
43826
|
-
if (run6.previousStateDigest !== void 0) {
|
|
43827
|
-
throw new ReleaseError(GATE_FAILED, "non-state run cannot claim previousStateDigest");
|
|
43828
|
-
}
|
|
43829
|
-
return;
|
|
43830
|
-
}
|
|
43831
|
-
let current = run6;
|
|
43832
|
-
let currentPath = realpathSync3(resolve8(runPath));
|
|
43833
|
-
let traversed = 0;
|
|
43834
|
-
while (true) {
|
|
43835
|
-
const sequence = current.stateSequence;
|
|
43836
|
-
const expectedName = `${String(sequence).padStart(6, "0")}.json`;
|
|
43837
|
-
if (!Number.isSafeInteger(sequence) || sequence < 0 || basename4(currentPath) !== expectedName || basename4(dirname4(currentPath)) !== "states") {
|
|
43838
|
-
throw new ReleaseError(GATE_FAILED, "run state sequence is not bound to its immutable states/<sequence>.json slot");
|
|
43839
|
-
}
|
|
43840
|
-
if (sequence === 0) {
|
|
43841
|
-
if (current.previousStateDigest !== void 0) {
|
|
43842
|
-
throw new ReleaseError(GATE_FAILED, "initial run state must not claim a predecessor digest");
|
|
43843
|
-
}
|
|
43844
|
-
return;
|
|
43845
|
-
}
|
|
43846
|
-
if (!current.previousStateDigest) {
|
|
43847
|
-
throw new ReleaseError(GATE_FAILED, "run state is missing previousStateDigest");
|
|
43848
|
-
}
|
|
43849
|
-
traversed += 1;
|
|
43850
|
-
if (traversed > 1e5) {
|
|
43851
|
-
throw new ReleaseError(GATE_FAILED, "run state predecessor chain exceeds maximum depth");
|
|
43852
|
-
}
|
|
43853
|
-
const previousPath = join7(dirname4(currentPath), `${String(sequence - 1).padStart(6, "0")}.json`);
|
|
43854
|
-
const previous = await loadRun(previousPath, {
|
|
43855
|
-
requireDigest: true,
|
|
43856
|
-
...options.production ? { authorityPlanPath: options.planPath } : {}
|
|
43857
|
-
});
|
|
43858
|
-
if (previous.stateSequence !== sequence - 1 || previous.runId !== current.runId || previous.command !== current.command || previous.planDigest !== current.planDigest) {
|
|
43859
|
-
throw new ReleaseError(GATE_FAILED, "run state predecessor does not belong to the same monotonic authority chain");
|
|
43860
|
-
}
|
|
43861
|
-
if (current.previousStateDigest !== previous.runDigest) {
|
|
43862
|
-
throw new ReleaseError(GATE_FAILED, "run state previousStateDigest does not match the immutable predecessor bytes");
|
|
43863
|
-
}
|
|
43864
|
-
current = previous;
|
|
43865
|
-
currentPath = realpathSync3(previousPath);
|
|
43866
|
-
}
|
|
43867
|
-
}
|
|
43868
|
-
function validateSourceRunEdge(child, parent) {
|
|
43869
|
-
if (child.command === "reconcile") {
|
|
43870
|
-
if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PARTIAL") {
|
|
43871
|
-
throw new ReleaseError(
|
|
43872
|
-
GATE_FAILED,
|
|
43873
|
-
"reconcile lineage must reference a PARTIAL publish or reconcile run",
|
|
43874
|
-
{ childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
|
|
43875
|
-
);
|
|
43876
|
-
}
|
|
43877
|
-
return;
|
|
43878
|
-
}
|
|
43879
|
-
if (child.command === "verify") {
|
|
43880
|
-
if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PUBLISHED") {
|
|
43881
|
-
throw new ReleaseError(
|
|
43882
|
-
GATE_FAILED,
|
|
43883
|
-
"verify lineage must reference a PUBLISHED publish or reconcile run",
|
|
43884
|
-
{ childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
|
|
43885
|
-
);
|
|
43886
|
-
}
|
|
43887
|
-
}
|
|
43888
|
-
if (child.command === "postverify") {
|
|
43889
|
-
if (parent.command !== "verify" || parent.status !== "VERIFIED") {
|
|
43890
|
-
throw new ReleaseError(
|
|
43891
|
-
GATE_FAILED,
|
|
43892
|
-
"postverify lineage must reference a VERIFIED verify run",
|
|
43893
|
-
{ childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
|
|
43894
|
-
);
|
|
43895
|
-
}
|
|
43896
|
-
}
|
|
43897
|
-
}
|
|
43898
|
-
async function validateRunLineage(run6, options = {}) {
|
|
43899
|
-
const { plan, planPath, runPath, production = false, maxDepth = 16 } = options;
|
|
43900
|
-
if (maxDepth < 0) {
|
|
43901
|
-
throw new ReleaseError(GATE_FAILED, "release run lineage exceeds maximum depth");
|
|
43902
|
-
}
|
|
43903
|
-
validateRun(run6, { requireDigest: production });
|
|
43904
|
-
if (production) assertImmutableRunAuthority(runPath, planPath, run6);
|
|
43905
|
-
await validateStatePredecessorChain(run6, runPath, { production, planPath });
|
|
43906
|
-
validateRunPlanDigest(run6, plan, { planPath });
|
|
43907
|
-
if (run6.command === "publish") return;
|
|
43908
|
-
if (!["reconcile", "verify", "postverify"].includes(run6.command)) {
|
|
43909
|
-
throw new ReleaseError(GATE_FAILED, `unsupported run command in lineage: ${run6.command}`);
|
|
43910
|
-
}
|
|
43911
|
-
if (!run6.sourceRunPath || !run6.sourceRunId || !run6.sourceRunDigest) {
|
|
43912
|
-
throw new ReleaseError(GATE_FAILED, `${run6.command} run is missing complete source run lineage`);
|
|
43913
|
-
}
|
|
43914
|
-
const parent = await loadRun(run6.sourceRunPath, {
|
|
43915
|
-
requireDigest: true,
|
|
43916
|
-
...production ? { authorityPlanPath: planPath } : {}
|
|
43917
|
-
});
|
|
43918
|
-
if (parent.runId !== run6.sourceRunId || parent.runDigest !== run6.sourceRunDigest) {
|
|
43919
|
-
throw new ReleaseError(
|
|
43920
|
-
GATE_FAILED,
|
|
43921
|
-
"source run lineage id/digest does not match referenced immutable run bytes",
|
|
43922
|
-
{ sourceRunPath: run6.sourceRunPath }
|
|
43923
|
-
);
|
|
43924
|
-
}
|
|
43925
|
-
validateSourceRunEdge(run6, parent);
|
|
43926
|
-
await validateRunLineage(parent, {
|
|
43927
|
-
plan,
|
|
43928
|
-
planPath,
|
|
43929
|
-
runPath: run6.sourceRunPath,
|
|
43930
|
-
production,
|
|
43931
|
-
maxDepth: maxDepth - 1
|
|
43932
|
-
});
|
|
43933
|
-
}
|
|
43934
|
-
function validateRunPlanDigest(run6, plan, options = {}) {
|
|
43935
|
-
const requirePresence = options.requirePresence !== false;
|
|
43936
|
-
const expectedDigest = computePlanDigest(plan);
|
|
43937
|
-
if (options.planPath) assertImmutablePlanAuthority(options.planPath, plan);
|
|
43938
|
-
if (!run6.planDigest) {
|
|
43939
|
-
if (requirePresence) {
|
|
43940
|
-
throw new ReleaseError(
|
|
43941
|
-
GATE_FAILED,
|
|
43942
|
-
"source run is missing required planDigest field",
|
|
43943
|
-
{ runId: run6.runId }
|
|
43944
|
-
);
|
|
43945
|
-
}
|
|
43946
|
-
return;
|
|
43947
|
-
}
|
|
43948
|
-
if (run6.planDigest !== expectedDigest) {
|
|
43949
|
-
throw new ReleaseError(
|
|
43950
|
-
GATE_FAILED,
|
|
43951
|
-
`run planDigest mismatch: run says ${String(run6.planDigest).slice(0, 16)}..., plan is ${expectedDigest.slice(0, 16)}...`,
|
|
43952
|
-
{ runDigest: run6.planDigest, planDigest: expectedDigest }
|
|
43953
|
-
);
|
|
43954
|
-
}
|
|
43955
|
-
if (plan.production && run6.planPath && options.planPath && resolve8(run6.planPath) !== resolve8(options.planPath)) {
|
|
43956
|
-
throw new ReleaseError(
|
|
43957
|
-
GATE_FAILED,
|
|
43958
|
-
"source run immutable plan path does not match the supplied plan authority",
|
|
43959
|
-
{ runPlanPath: run6.planPath, suppliedPlanPath: options.planPath }
|
|
43960
|
-
);
|
|
43961
|
-
}
|
|
43962
|
-
}
|
|
43963
|
-
function validateRunCheckpointMapping(run6, planActions) {
|
|
43964
|
-
const seenPlanActionIds = /* @__PURE__ */ new Set();
|
|
43965
|
-
for (const action of planActions) {
|
|
43966
|
-
if (seenPlanActionIds.has(action.id)) {
|
|
43967
|
-
throw new ReleaseError(
|
|
43968
|
-
GATE_FAILED,
|
|
43969
|
-
`release plan has duplicate action id "${action.id}"`,
|
|
43970
|
-
{ actionId: action.id }
|
|
43971
|
-
);
|
|
43972
|
-
}
|
|
43973
|
-
seenPlanActionIds.add(action.id);
|
|
43974
|
-
}
|
|
43975
|
-
const planActionIds = new Set(planActions.map((a) => a.id));
|
|
43976
|
-
const planActionsById = new Map(planActions.map((action) => [action.id, action]));
|
|
43977
|
-
const seenCheckpointIds = /* @__PURE__ */ new Set();
|
|
43978
|
-
for (const cp4 of run6.checkpoints) {
|
|
43979
|
-
if (seenCheckpointIds.has(cp4.actionId)) {
|
|
43980
|
-
throw new ReleaseError(
|
|
43981
|
-
GATE_FAILED,
|
|
43982
|
-
`source run has duplicate checkpoint for action "${cp4.actionId}"`,
|
|
43983
|
-
{ actionId: cp4.actionId }
|
|
43984
|
-
);
|
|
43985
|
-
}
|
|
43986
|
-
seenCheckpointIds.add(cp4.actionId);
|
|
43987
|
-
}
|
|
43988
|
-
const runCheckpointIds = new Set(run6.checkpoints.map((cp4) => cp4.actionId));
|
|
43989
|
-
for (const action of planActions) {
|
|
43990
|
-
if (!runCheckpointIds.has(action.id)) {
|
|
43991
|
-
throw new ReleaseError(
|
|
43992
|
-
GATE_FAILED,
|
|
43993
|
-
`source run missing checkpoint for plan action "${action.id}"`,
|
|
43994
|
-
{ actionId: action.id }
|
|
43995
|
-
);
|
|
43996
|
-
}
|
|
43997
|
-
}
|
|
43998
|
-
for (const cp4 of run6.checkpoints) {
|
|
43999
|
-
if (!planActionIds.has(cp4.actionId)) {
|
|
44000
|
-
throw new ReleaseError(
|
|
44001
|
-
GATE_FAILED,
|
|
44002
|
-
`source run has checkpoint for unknown action "${cp4.actionId}"`,
|
|
44003
|
-
{ actionId: cp4.actionId }
|
|
44004
|
-
);
|
|
44005
|
-
}
|
|
44006
|
-
const action = planActionsById.get(cp4.actionId);
|
|
44007
|
-
if (cp4.actionType !== action.type) {
|
|
44008
|
-
throw new ReleaseError(
|
|
44009
|
-
GATE_FAILED,
|
|
44010
|
-
`source run checkpoint actionType mismatch for action "${cp4.actionId}": run says "${cp4.actionType}", plan says "${action.type}"`,
|
|
44011
|
-
{ actionId: cp4.actionId, runActionType: cp4.actionType, planActionType: action.type }
|
|
44012
|
-
);
|
|
44013
|
-
}
|
|
44014
|
-
}
|
|
44015
|
-
}
|
|
44016
|
-
function computeRunDigest(run6) {
|
|
44017
|
-
const { runDigest: _, ...rest } = run6;
|
|
44018
|
-
return sha256Hex(JSON.stringify(rest, null, 2));
|
|
44019
|
-
}
|
|
44020
|
-
function sealRun(run6) {
|
|
44021
|
-
const sealed = { ...run6 };
|
|
44022
|
-
sealed.runDigest = computeRunDigest(sealed);
|
|
44023
|
-
validateRun(sealed, { requireDigest: true });
|
|
44024
|
-
return sealed;
|
|
44025
|
-
}
|
|
44026
|
-
async function writeRunAtomic(runPath, run6) {
|
|
44027
|
-
const sealed = sealRun(run6);
|
|
44028
|
-
const json = JSON.stringify(sealed, null, 2);
|
|
44029
|
-
const dir = dirname4(runPath);
|
|
44030
|
-
await mkdir10(dir, { recursive: true });
|
|
44031
|
-
try {
|
|
44032
|
-
await publishFileExclusive(dir, basename4(runPath), json, { mode: 384 });
|
|
44033
|
-
} catch (cause) {
|
|
44034
|
-
if (cause?.details?.kind === HARNESS_ERROR_KINDS.EXCLUSIVE_PUBLISH_CONFLICT) {
|
|
44035
|
-
const existing = await readFile18(runPath, "utf8").catch(() => null);
|
|
44036
|
-
if (existing === json) return Object.freeze(sealed);
|
|
44037
|
-
throw new ReleaseError(
|
|
44038
|
-
GATE_FAILED,
|
|
44039
|
-
"run file already exists with different bytes; exclusive-create rejected overwrite",
|
|
44040
|
-
{ runPath }
|
|
44041
|
-
);
|
|
44042
|
-
}
|
|
44043
|
-
throw new ReleaseError(
|
|
44044
|
-
GATE_FAILED,
|
|
44045
|
-
`run file write failed: ${cause?.message ?? String(cause)}`,
|
|
44046
|
-
{ runPath }
|
|
44047
|
-
);
|
|
44048
|
-
}
|
|
44049
|
-
return Object.freeze(sealed);
|
|
44050
|
-
}
|
|
44051
|
-
async function appendRunState(runDir, sequence, run6) {
|
|
44052
|
-
if (!Number.isSafeInteger(sequence) || sequence < 0) {
|
|
44053
|
-
throw new ReleaseError(GATE_FAILED, "run state sequence must be a non-negative integer");
|
|
44054
|
-
}
|
|
44055
|
-
const statesDir = join7(runDir, "states");
|
|
44056
|
-
let previousStateDigest;
|
|
44057
|
-
if (sequence > 0) {
|
|
44058
|
-
const previousPath = join7(statesDir, `${String(sequence - 1).padStart(6, "0")}.json`);
|
|
44059
|
-
const previous = await loadRun(previousPath, { requireDigest: true });
|
|
44060
|
-
if (previous.stateSequence !== sequence - 1 || previous.runId !== run6.runId || previous.command !== run6.command || previous.planDigest !== run6.planDigest) {
|
|
44061
|
-
throw new ReleaseError(
|
|
44062
|
-
GATE_FAILED,
|
|
44063
|
-
"run state predecessor does not belong to the same monotonic authority chain",
|
|
44064
|
-
{ sequence, previousPath }
|
|
44065
|
-
);
|
|
44066
|
-
}
|
|
44067
|
-
previousStateDigest = previous.runDigest;
|
|
44068
|
-
}
|
|
44069
|
-
const { previousStateDigest: _discarded, ...nextRun } = run6;
|
|
44070
|
-
const state = sealRun({
|
|
44071
|
-
...nextRun,
|
|
44072
|
-
stateSequence: sequence,
|
|
44073
|
-
...previousStateDigest ? { previousStateDigest } : {}
|
|
44074
|
-
});
|
|
44075
|
-
const statePath = join7(statesDir, `${String(sequence).padStart(6, "0")}.json`);
|
|
44076
|
-
await writeRunAtomic(statePath, state);
|
|
44077
|
-
return Object.freeze({ state, statePath });
|
|
44078
|
-
}
|
|
44079
|
-
var import_ajv4, import_ajv_formats2, RELEASE_RUN_SCHEMA, ajv2, validateRunSchema;
|
|
44080
|
-
var init_run = __esm({
|
|
44081
|
-
async "src/core/run.mjs"() {
|
|
44082
|
-
import_ajv4 = __toESM(require_ajv(), 1);
|
|
44083
|
-
import_ajv_formats2 = __toESM(require_dist3(), 1);
|
|
44084
|
-
await init_plan();
|
|
44085
|
-
init_digest();
|
|
44086
|
-
init_errors3();
|
|
44087
|
-
init_trusted_resource();
|
|
44088
|
-
init_foundation_inflight();
|
|
44089
|
-
RELEASE_RUN_SCHEMA = JSON.parse((await readTrustedPackageResource(
|
|
44090
|
-
"schemas/release-run.schema.json"
|
|
44091
|
-
)).toString("utf8"));
|
|
44092
|
-
__name(resolveDefaultRunDir, "resolveDefaultRunDir");
|
|
44093
|
-
ajv2 = new import_ajv4.default({ allErrors: true, strict: false });
|
|
44094
|
-
(0, import_ajv_formats2.default)(ajv2);
|
|
44095
|
-
validateRunSchema = ajv2.compile(RELEASE_RUN_SCHEMA);
|
|
44096
|
-
__name(validateRun, "validateRun");
|
|
44097
|
-
__name(resolveRunPath, "resolveRunPath");
|
|
44098
|
-
__name(loadRun, "loadRun");
|
|
44099
|
-
__name(assertImmutableRunAuthority, "assertImmutableRunAuthority");
|
|
44100
|
-
__name(createProductionRunDir, "createProductionRunDir");
|
|
44101
|
-
__name(createProductionPrepareRunDir, "createProductionPrepareRunDir");
|
|
44102
|
-
__name(createProductionRunDirWithinAuthority, "createProductionRunDirWithinAuthority");
|
|
44103
|
-
__name(validateStatePredecessorChain, "validateStatePredecessorChain");
|
|
44104
|
-
__name(validateSourceRunEdge, "validateSourceRunEdge");
|
|
44105
|
-
__name(validateRunLineage, "validateRunLineage");
|
|
44106
|
-
__name(validateRunPlanDigest, "validateRunPlanDigest");
|
|
44107
|
-
__name(validateRunCheckpointMapping, "validateRunCheckpointMapping");
|
|
44108
|
-
__name(computeRunDigest, "computeRunDigest");
|
|
44109
|
-
__name(sealRun, "sealRun");
|
|
44110
|
-
__name(writeRunAtomic, "writeRunAtomic");
|
|
44111
|
-
__name(appendRunState, "appendRunState");
|
|
44112
|
-
}
|
|
44113
|
-
});
|
|
44114
|
-
|
|
44115
43522
|
// src/core/baseline.mjs
|
|
44116
43523
|
import { execFile as execFileCb2 } from "node:child_process";
|
|
44117
43524
|
import { promisify as promisify2 } from "node:util";
|
|
44118
43525
|
import { createHash as createHash9 } from "node:crypto";
|
|
44119
|
-
import { lstat as
|
|
43526
|
+
import { lstat as lstat27, open as open9, readlink as readlink3 } from "node:fs/promises";
|
|
44120
43527
|
import { constants as fsConstants } from "node:fs";
|
|
44121
43528
|
import path38 from "node:path";
|
|
44122
43529
|
function isControlPlanePath(p) {
|
|
@@ -44200,7 +43607,7 @@ async function computeWorkspaceDigest(root) {
|
|
|
44200
43607
|
`Refusing to read path outside repository root: ${relPath} resolves to ${absPath}`
|
|
44201
43608
|
);
|
|
44202
43609
|
}
|
|
44203
|
-
const stat22 = await
|
|
43610
|
+
const stat22 = await lstat27(absPath);
|
|
44204
43611
|
let type;
|
|
44205
43612
|
if (stat22.isSymbolicLink()) {
|
|
44206
43613
|
type = "symlink";
|
|
@@ -44323,8 +43730,8 @@ var init_baseline2 = __esm({
|
|
|
44323
43730
|
});
|
|
44324
43731
|
|
|
44325
43732
|
// src/core/approval.mjs
|
|
44326
|
-
import { readFile as
|
|
44327
|
-
import { basename as
|
|
43733
|
+
import { readFile as readFile18 } from "node:fs/promises";
|
|
43734
|
+
import { basename as basename4, dirname as dirname4, join as join7, resolve as resolve8 } from "node:path";
|
|
44328
43735
|
function validateApprovalRecordSchema(approval) {
|
|
44329
43736
|
if (validateApprovalSchema(approval)) return;
|
|
44330
43737
|
const errors = validateApprovalSchema.errors ?? [];
|
|
@@ -44398,9 +43805,9 @@ function assertImmutableApprovalAuthority(approvalPath, plan, rawApproval) {
|
|
|
44398
43805
|
if (!plan?.production) return;
|
|
44399
43806
|
const planDigest = computePlanDigest(plan);
|
|
44400
43807
|
const approvalDigest = computeApprovalDigest(rawApproval);
|
|
44401
|
-
const absolute2 =
|
|
44402
|
-
const planDirectory =
|
|
44403
|
-
if (
|
|
43808
|
+
const absolute2 = resolve8(approvalPath);
|
|
43809
|
+
const planDirectory = dirname4(absolute2);
|
|
43810
|
+
if (basename4(absolute2) !== `${approvalDigest}.json` || basename4(planDirectory) !== planDigest || basename4(dirname4(planDirectory)) !== "approvals") {
|
|
44404
43811
|
throw new ReleaseError(
|
|
44405
43812
|
GATE_FAILED,
|
|
44406
43813
|
"production commands require approvals/<planDigest>/<approvalDigest>.json immutable authority",
|
|
@@ -44586,21 +43993,21 @@ function validateApproval(plan, approval, options = {}) {
|
|
|
44586
43993
|
requireUnexpired: options.requireUnexpired
|
|
44587
43994
|
});
|
|
44588
43995
|
}
|
|
44589
|
-
var
|
|
43996
|
+
var import_ajv4, import_ajv_formats2, approvalSchema, approvalAjv, validateApprovalSchema, MAX_APPROVAL_MS, CLOCK_SKEW_TOLERANCE_MS;
|
|
44590
43997
|
var init_approval = __esm({
|
|
44591
43998
|
async "src/core/approval.mjs"() {
|
|
44592
43999
|
init_errors3();
|
|
44593
44000
|
await init_plan();
|
|
44594
44001
|
init_src2();
|
|
44595
|
-
|
|
44596
|
-
|
|
44002
|
+
import_ajv4 = __toESM(require_ajv(), 1);
|
|
44003
|
+
import_ajv_formats2 = __toESM(require_dist3(), 1);
|
|
44597
44004
|
init_baseline2();
|
|
44598
44005
|
init_trusted_resource();
|
|
44599
44006
|
approvalSchema = JSON.parse((await readTrustedPackageResource(
|
|
44600
44007
|
"schemas/approval-record.schema.json"
|
|
44601
44008
|
)).toString("utf8"));
|
|
44602
|
-
approvalAjv = new
|
|
44603
|
-
(0,
|
|
44009
|
+
approvalAjv = new import_ajv4.default({ allErrors: true, strict: false });
|
|
44010
|
+
(0, import_ajv_formats2.default)(approvalAjv);
|
|
44604
44011
|
validateApprovalSchema = approvalAjv.compile(approvalSchema);
|
|
44605
44012
|
__name(validateApprovalRecordSchema, "validateApprovalRecordSchema");
|
|
44606
44013
|
__name(computeApprovalDigest, "computeApprovalDigest");
|
|
@@ -44707,6 +44114,977 @@ var init_checkpoints = __esm({
|
|
|
44707
44114
|
}
|
|
44708
44115
|
});
|
|
44709
44116
|
|
|
44117
|
+
// src/core/run.mjs
|
|
44118
|
+
var run_exports = {};
|
|
44119
|
+
__export(run_exports, {
|
|
44120
|
+
appendRunState: () => appendRunState,
|
|
44121
|
+
assertImmutableRunAuthority: () => assertImmutableRunAuthority,
|
|
44122
|
+
computeRunDigest: () => computeRunDigest,
|
|
44123
|
+
createProductionPrepareRunDir: () => createProductionPrepareRunDir,
|
|
44124
|
+
createProductionRunDir: () => createProductionRunDir,
|
|
44125
|
+
loadRun: () => loadRun,
|
|
44126
|
+
resolveDefaultRunDir: () => resolveDefaultRunDir,
|
|
44127
|
+
resolveRunPath: () => resolveRunPath,
|
|
44128
|
+
validateRun: () => validateRun,
|
|
44129
|
+
validateRunCheckpointMapping: () => validateRunCheckpointMapping,
|
|
44130
|
+
validateRunLineage: () => validateRunLineage,
|
|
44131
|
+
validateRunPlanDigest: () => validateRunPlanDigest,
|
|
44132
|
+
validateSourceRunEdge: () => validateSourceRunEdge,
|
|
44133
|
+
writeRunAtomic: () => writeRunAtomic
|
|
44134
|
+
});
|
|
44135
|
+
import { lstat as lstat28, mkdir as mkdir10, readFile as readFile19, stat as stat10 } from "node:fs/promises";
|
|
44136
|
+
import { realpathSync as realpathSync3 } from "node:fs";
|
|
44137
|
+
import { dirname as dirname5, join as join8, resolve as resolve9, basename as basename5, relative as relative7, isAbsolute as isAbsolute5 } from "node:path";
|
|
44138
|
+
function resolveDefaultRunDir(planPath, command2, runId = `${command2}-${Date.now()}`) {
|
|
44139
|
+
const absolute2 = resolve9(planPath);
|
|
44140
|
+
const fileName = basename5(absolute2);
|
|
44141
|
+
const parentDir = dirname5(absolute2);
|
|
44142
|
+
if (fileName === "release-plan.json") {
|
|
44143
|
+
return `${parentDir}/runs/${runId}`;
|
|
44144
|
+
}
|
|
44145
|
+
if (basename5(parentDir) === "plans") {
|
|
44146
|
+
const releaseDir = dirname5(parentDir);
|
|
44147
|
+
return join8(releaseDir, "runs", runId);
|
|
44148
|
+
}
|
|
44149
|
+
return `${parentDir}/runs/${runId}`;
|
|
44150
|
+
}
|
|
44151
|
+
function validateRun(run6, options = {}) {
|
|
44152
|
+
const valid = validateRunSchema(run6);
|
|
44153
|
+
if (!valid) {
|
|
44154
|
+
const errors = validateRunSchema.errors ?? [];
|
|
44155
|
+
const summary = errors.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
|
|
44156
|
+
throw new ReleaseError(
|
|
44157
|
+
GATE_FAILED,
|
|
44158
|
+
`release run schema validation failed: ${summary}`,
|
|
44159
|
+
{ validationErrors: errors }
|
|
44160
|
+
);
|
|
44161
|
+
}
|
|
44162
|
+
const actionIds = /* @__PURE__ */ new Set();
|
|
44163
|
+
for (const checkpoint of run6.checkpoints ?? []) {
|
|
44164
|
+
if (actionIds.has(checkpoint.actionId)) {
|
|
44165
|
+
throw new ReleaseError(
|
|
44166
|
+
GATE_FAILED,
|
|
44167
|
+
`release run has duplicate checkpoint for action "${checkpoint.actionId}"`,
|
|
44168
|
+
{ actionId: checkpoint.actionId }
|
|
44169
|
+
);
|
|
44170
|
+
}
|
|
44171
|
+
actionIds.add(checkpoint.actionId);
|
|
44172
|
+
}
|
|
44173
|
+
if (options.requireDigest === true && !run6.runDigest) {
|
|
44174
|
+
throw new ReleaseError(GATE_FAILED, "release run is missing required runDigest");
|
|
44175
|
+
}
|
|
44176
|
+
if (run6.runDigest && run6.runDigest !== computeRunDigest(run6)) {
|
|
44177
|
+
throw new ReleaseError(
|
|
44178
|
+
GATE_FAILED,
|
|
44179
|
+
"release run digest does not match its content",
|
|
44180
|
+
{ expected: computeRunDigest(run6), actual: run6.runDigest }
|
|
44181
|
+
);
|
|
44182
|
+
}
|
|
44183
|
+
}
|
|
44184
|
+
async function resolveRunPath(runPath) {
|
|
44185
|
+
let stats;
|
|
44186
|
+
try {
|
|
44187
|
+
stats = await lstat28(runPath);
|
|
44188
|
+
} catch (err) {
|
|
44189
|
+
if (err?.code === "ENOENT") return runPath;
|
|
44190
|
+
throw new ReleaseError(
|
|
44191
|
+
GATE_FAILED,
|
|
44192
|
+
`cannot inspect release run path: ${err.message}`,
|
|
44193
|
+
{ runPath, cause: err.code }
|
|
44194
|
+
);
|
|
44195
|
+
}
|
|
44196
|
+
if (stats.isFile()) return runPath;
|
|
44197
|
+
if (stats.isSymbolicLink()) {
|
|
44198
|
+
let targetStats;
|
|
44199
|
+
try {
|
|
44200
|
+
targetStats = await stat10(runPath);
|
|
44201
|
+
} catch (err) {
|
|
44202
|
+
if (err?.code === "ENOENT") return runPath;
|
|
44203
|
+
throw new ReleaseError(
|
|
44204
|
+
GATE_FAILED,
|
|
44205
|
+
`cannot inspect release run path: ${err.message}`,
|
|
44206
|
+
{ runPath, cause: err.code }
|
|
44207
|
+
);
|
|
44208
|
+
}
|
|
44209
|
+
if (targetStats.isDirectory()) {
|
|
44210
|
+
throw new ReleaseError(
|
|
44211
|
+
GATE_FAILED,
|
|
44212
|
+
"release run path is a symlinked directory; pass the run file (release-run.json) or the real run directory",
|
|
44213
|
+
{ runPath }
|
|
44214
|
+
);
|
|
44215
|
+
}
|
|
44216
|
+
return runPath;
|
|
44217
|
+
}
|
|
44218
|
+
if (stats.isDirectory()) {
|
|
44219
|
+
const candidate = join8(runPath, "release-run.json");
|
|
44220
|
+
try {
|
|
44221
|
+
const candidateStats = await lstat28(candidate);
|
|
44222
|
+
if (!candidateStats.isFile()) {
|
|
44223
|
+
throw new ReleaseError(
|
|
44224
|
+
GATE_FAILED,
|
|
44225
|
+
`release run directory does not contain a regular release-run.json: ${runPath}`,
|
|
44226
|
+
{ runPath, expected: "release-run.json" }
|
|
44227
|
+
);
|
|
44228
|
+
}
|
|
44229
|
+
} catch (err) {
|
|
44230
|
+
if (err instanceof ReleaseError) throw err;
|
|
44231
|
+
throw new ReleaseError(
|
|
44232
|
+
GATE_FAILED,
|
|
44233
|
+
`release run directory must contain release-run.json: ${err.message}`,
|
|
44234
|
+
{ runPath, expected: "release-run.json", cause: err.code }
|
|
44235
|
+
);
|
|
44236
|
+
}
|
|
44237
|
+
return candidate;
|
|
44238
|
+
}
|
|
44239
|
+
return runPath;
|
|
44240
|
+
}
|
|
44241
|
+
async function loadRun(runPath, options = {}) {
|
|
44242
|
+
const resolvedRunPath = await resolveRunPath(runPath);
|
|
44243
|
+
let raw;
|
|
44244
|
+
try {
|
|
44245
|
+
raw = await readFile19(resolvedRunPath, "utf8");
|
|
44246
|
+
} catch (err) {
|
|
44247
|
+
throw new ReleaseError(
|
|
44248
|
+
GATE_FAILED,
|
|
44249
|
+
`cannot read release run: ${err.message}`,
|
|
44250
|
+
{ runPath: resolvedRunPath, cause: err.code }
|
|
44251
|
+
);
|
|
44252
|
+
}
|
|
44253
|
+
let run6;
|
|
44254
|
+
try {
|
|
44255
|
+
run6 = JSON.parse(raw);
|
|
44256
|
+
} catch (err) {
|
|
44257
|
+
throw new ReleaseError(
|
|
44258
|
+
GATE_FAILED,
|
|
44259
|
+
`release run is not valid JSON: ${err.message}`,
|
|
44260
|
+
{ runPath: resolvedRunPath }
|
|
44261
|
+
);
|
|
44262
|
+
}
|
|
44263
|
+
validateRun(run6, options);
|
|
44264
|
+
if (options.authorityPlanPath) {
|
|
44265
|
+
assertImmutableRunAuthority(resolvedRunPath, options.authorityPlanPath, run6);
|
|
44266
|
+
}
|
|
44267
|
+
return run6;
|
|
44268
|
+
}
|
|
44269
|
+
function assertImmutableRunAuthority(runPath, planPath, run6) {
|
|
44270
|
+
const absolutePlan = realpathSync3(resolve9(planPath));
|
|
44271
|
+
const planDir = dirname5(absolutePlan);
|
|
44272
|
+
if (basename5(planDir) !== "plans") {
|
|
44273
|
+
throw new ReleaseError(GATE_FAILED, "run authority requires an immutable plans/<digest>.json plan path");
|
|
44274
|
+
}
|
|
44275
|
+
let authorityRoot = dirname5(planDir);
|
|
44276
|
+
let cursor = planDir;
|
|
44277
|
+
while (dirname5(cursor) !== cursor) {
|
|
44278
|
+
if (basename5(cursor) === ".release-skill") {
|
|
44279
|
+
authorityRoot = cursor;
|
|
44280
|
+
break;
|
|
44281
|
+
}
|
|
44282
|
+
cursor = dirname5(cursor);
|
|
44283
|
+
}
|
|
44284
|
+
const runsDir = join8(authorityRoot, "runs");
|
|
44285
|
+
const absoluteRun = realpathSync3(resolve9(runPath));
|
|
44286
|
+
const rel = relative7(runsDir, absoluteRun);
|
|
44287
|
+
if (isAbsolute5(rel) || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
44288
|
+
throw new ReleaseError(GATE_FAILED, "production run authority must be inside the plan sibling runs/ directory");
|
|
44289
|
+
}
|
|
44290
|
+
const fileName = basename5(absoluteRun);
|
|
44291
|
+
const isFinal = fileName === "release-run.json";
|
|
44292
|
+
const isState = /^\d{6}\.json$/.test(fileName) && basename5(dirname5(absoluteRun)) === "states" && run6.stateSequence === Number(fileName.slice(0, 6));
|
|
44293
|
+
if (!isFinal && !isState) {
|
|
44294
|
+
throw new ReleaseError(
|
|
44295
|
+
GATE_FAILED,
|
|
44296
|
+
"production run authority must be release-run.json or a bound states/<sequence>.json snapshot",
|
|
44297
|
+
{ runPath }
|
|
44298
|
+
);
|
|
44299
|
+
}
|
|
44300
|
+
}
|
|
44301
|
+
async function createProductionRunDir(runDir, planPath) {
|
|
44302
|
+
const absolutePlan = realpathSync3(resolve9(planPath));
|
|
44303
|
+
const planDir = dirname5(absolutePlan);
|
|
44304
|
+
if (basename5(planDir) !== "plans") {
|
|
44305
|
+
throw new ReleaseError(GATE_FAILED, "production run directory requires an immutable plans/<digest>.json authority");
|
|
44306
|
+
}
|
|
44307
|
+
const authorityRoot = dirname5(planDir);
|
|
44308
|
+
return createProductionRunDirWithinAuthority(runDir, authorityRoot);
|
|
44309
|
+
}
|
|
44310
|
+
async function createProductionPrepareRunDir(runDir, releaseDir) {
|
|
44311
|
+
const authorityRoot = resolve9(releaseDir);
|
|
44312
|
+
let authorityStat;
|
|
44313
|
+
try {
|
|
44314
|
+
authorityStat = await lstat28(authorityRoot);
|
|
44315
|
+
} catch (error) {
|
|
44316
|
+
throw new ReleaseError(GATE_FAILED, "cannot inspect production .release-skill authority root", {
|
|
44317
|
+
releaseDir: authorityRoot,
|
|
44318
|
+
cause: error.code
|
|
44319
|
+
});
|
|
44320
|
+
}
|
|
44321
|
+
if (authorityStat.isSymbolicLink() || !authorityStat.isDirectory()) {
|
|
44322
|
+
throw new ReleaseError(
|
|
44323
|
+
GATE_FAILED,
|
|
44324
|
+
"production .release-skill authority root must be a real directory, not a symlink or special file",
|
|
44325
|
+
{ releaseDir: authorityRoot }
|
|
44326
|
+
);
|
|
44327
|
+
}
|
|
44328
|
+
const physicalAuthorityRoot = realpathSync3(authorityRoot);
|
|
44329
|
+
if (physicalAuthorityRoot !== authorityRoot) {
|
|
44330
|
+
throw new ReleaseError(
|
|
44331
|
+
GATE_FAILED,
|
|
44332
|
+
"production .release-skill authority root physical identity does not match the project authority",
|
|
44333
|
+
{ releaseDir: authorityRoot, physicalReleaseDir: physicalAuthorityRoot }
|
|
44334
|
+
);
|
|
44335
|
+
}
|
|
44336
|
+
return createProductionRunDirWithinAuthority(runDir, physicalAuthorityRoot);
|
|
44337
|
+
}
|
|
44338
|
+
async function createProductionRunDirWithinAuthority(runDir, authorityRoot) {
|
|
44339
|
+
const runsDir = join8(authorityRoot, "runs");
|
|
44340
|
+
let runsStat;
|
|
44341
|
+
try {
|
|
44342
|
+
runsStat = await lstat28(runsDir);
|
|
44343
|
+
} catch (error) {
|
|
44344
|
+
if (error.code !== "ENOENT") {
|
|
44345
|
+
throw new ReleaseError(GATE_FAILED, "cannot inspect production runs authority root", {
|
|
44346
|
+
runsDir,
|
|
44347
|
+
cause: error.code
|
|
44348
|
+
});
|
|
44349
|
+
}
|
|
44350
|
+
try {
|
|
44351
|
+
await mkdir10(runsDir, { mode: 448 });
|
|
44352
|
+
} catch (mkdirError) {
|
|
44353
|
+
if (mkdirError.code !== "EEXIST") {
|
|
44354
|
+
throw new ReleaseError(GATE_FAILED, "cannot create production runs authority root", {
|
|
44355
|
+
runsDir,
|
|
44356
|
+
cause: mkdirError.code
|
|
44357
|
+
});
|
|
44358
|
+
}
|
|
44359
|
+
}
|
|
44360
|
+
runsStat = await lstat28(runsDir);
|
|
44361
|
+
}
|
|
44362
|
+
if (runsStat.isSymbolicLink() || !runsStat.isDirectory()) {
|
|
44363
|
+
throw new ReleaseError(
|
|
44364
|
+
GATE_FAILED,
|
|
44365
|
+
"production .release-skill/runs authority root must be a real directory, not a symlink or special file",
|
|
44366
|
+
{ runsDir }
|
|
44367
|
+
);
|
|
44368
|
+
}
|
|
44369
|
+
const physicalRunsDir = realpathSync3(runsDir);
|
|
44370
|
+
if (physicalRunsDir !== runsDir) {
|
|
44371
|
+
throw new ReleaseError(
|
|
44372
|
+
GATE_FAILED,
|
|
44373
|
+
"production .release-skill/runs authority root physical identity does not match the plan authority",
|
|
44374
|
+
{ runsDir, physicalRunsDir }
|
|
44375
|
+
);
|
|
44376
|
+
}
|
|
44377
|
+
const requested = resolve9(runDir);
|
|
44378
|
+
let physicalParent;
|
|
44379
|
+
try {
|
|
44380
|
+
physicalParent = realpathSync3(dirname5(requested));
|
|
44381
|
+
} catch (error) {
|
|
44382
|
+
throw new ReleaseError(GATE_FAILED, "production run directory parent is unavailable", {
|
|
44383
|
+
runDir,
|
|
44384
|
+
cause: error.code
|
|
44385
|
+
});
|
|
44386
|
+
}
|
|
44387
|
+
if (physicalParent !== physicalRunsDir || basename5(requested) === "" || basename5(requested) === "." || basename5(requested) === "..") {
|
|
44388
|
+
throw new ReleaseError(
|
|
44389
|
+
GATE_FAILED,
|
|
44390
|
+
"production run directory must be a fresh direct child of the immutable .release-skill/runs authority",
|
|
44391
|
+
{ runDir, runsDir: physicalRunsDir }
|
|
44392
|
+
);
|
|
44393
|
+
}
|
|
44394
|
+
try {
|
|
44395
|
+
await lstat28(requested);
|
|
44396
|
+
throw new ReleaseError(GATE_FAILED, "production run directory already exists; authority directories cannot be reused", { runDir });
|
|
44397
|
+
} catch (error) {
|
|
44398
|
+
if (error instanceof ReleaseError) throw error;
|
|
44399
|
+
if (error.code !== "ENOENT") {
|
|
44400
|
+
throw new ReleaseError(GATE_FAILED, "cannot establish fresh production run directory", {
|
|
44401
|
+
runDir,
|
|
44402
|
+
cause: error.code
|
|
44403
|
+
});
|
|
44404
|
+
}
|
|
44405
|
+
}
|
|
44406
|
+
try {
|
|
44407
|
+
await mkdir10(requested, { mode: 448 });
|
|
44408
|
+
} catch (error) {
|
|
44409
|
+
throw new ReleaseError(GATE_FAILED, "cannot exclusively create production run directory", {
|
|
44410
|
+
runDir,
|
|
44411
|
+
cause: error.code
|
|
44412
|
+
});
|
|
44413
|
+
}
|
|
44414
|
+
const physicalRunDir = realpathSync3(requested);
|
|
44415
|
+
if (dirname5(physicalRunDir) !== physicalRunsDir) {
|
|
44416
|
+
throw new ReleaseError(GATE_FAILED, "production run directory escaped its immutable authority after creation", { runDir });
|
|
44417
|
+
}
|
|
44418
|
+
return physicalRunDir;
|
|
44419
|
+
}
|
|
44420
|
+
async function validateStatePredecessorChain(run6, runPath, options = {}) {
|
|
44421
|
+
if (run6.stateSequence === void 0) {
|
|
44422
|
+
if (run6.previousStateDigest !== void 0) {
|
|
44423
|
+
throw new ReleaseError(GATE_FAILED, "non-state run cannot claim previousStateDigest");
|
|
44424
|
+
}
|
|
44425
|
+
return;
|
|
44426
|
+
}
|
|
44427
|
+
let current = run6;
|
|
44428
|
+
let currentPath = realpathSync3(resolve9(runPath));
|
|
44429
|
+
let traversed = 0;
|
|
44430
|
+
while (true) {
|
|
44431
|
+
const sequence = current.stateSequence;
|
|
44432
|
+
const expectedName = `${String(sequence).padStart(6, "0")}.json`;
|
|
44433
|
+
if (!Number.isSafeInteger(sequence) || sequence < 0 || basename5(currentPath) !== expectedName || basename5(dirname5(currentPath)) !== "states") {
|
|
44434
|
+
throw new ReleaseError(GATE_FAILED, "run state sequence is not bound to its immutable states/<sequence>.json slot");
|
|
44435
|
+
}
|
|
44436
|
+
if (sequence === 0) {
|
|
44437
|
+
if (current.previousStateDigest !== void 0) {
|
|
44438
|
+
throw new ReleaseError(GATE_FAILED, "initial run state must not claim a predecessor digest");
|
|
44439
|
+
}
|
|
44440
|
+
return;
|
|
44441
|
+
}
|
|
44442
|
+
if (!current.previousStateDigest) {
|
|
44443
|
+
throw new ReleaseError(GATE_FAILED, "run state is missing previousStateDigest");
|
|
44444
|
+
}
|
|
44445
|
+
traversed += 1;
|
|
44446
|
+
if (traversed > 1e5) {
|
|
44447
|
+
throw new ReleaseError(GATE_FAILED, "run state predecessor chain exceeds maximum depth");
|
|
44448
|
+
}
|
|
44449
|
+
const previousPath = join8(dirname5(currentPath), `${String(sequence - 1).padStart(6, "0")}.json`);
|
|
44450
|
+
const previous = await loadRun(previousPath, {
|
|
44451
|
+
requireDigest: true,
|
|
44452
|
+
...options.production ? { authorityPlanPath: options.planPath } : {}
|
|
44453
|
+
});
|
|
44454
|
+
if (previous.stateSequence !== sequence - 1 || previous.runId !== current.runId || previous.command !== current.command || previous.planDigest !== current.planDigest) {
|
|
44455
|
+
throw new ReleaseError(GATE_FAILED, "run state predecessor does not belong to the same monotonic authority chain");
|
|
44456
|
+
}
|
|
44457
|
+
if (current.previousStateDigest !== previous.runDigest) {
|
|
44458
|
+
throw new ReleaseError(GATE_FAILED, "run state previousStateDigest does not match the immutable predecessor bytes");
|
|
44459
|
+
}
|
|
44460
|
+
current = previous;
|
|
44461
|
+
currentPath = realpathSync3(previousPath);
|
|
44462
|
+
}
|
|
44463
|
+
}
|
|
44464
|
+
function validateSourceRunEdge(child, parent) {
|
|
44465
|
+
if (child.command === "reconcile") {
|
|
44466
|
+
if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PARTIAL") {
|
|
44467
|
+
throw new ReleaseError(
|
|
44468
|
+
GATE_FAILED,
|
|
44469
|
+
"reconcile lineage must reference a PARTIAL publish or reconcile run",
|
|
44470
|
+
{ childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
|
|
44471
|
+
);
|
|
44472
|
+
}
|
|
44473
|
+
return;
|
|
44474
|
+
}
|
|
44475
|
+
if (child.command === "verify") {
|
|
44476
|
+
if (!["publish", "reconcile"].includes(parent.command) || parent.status !== "PUBLISHED") {
|
|
44477
|
+
throw new ReleaseError(
|
|
44478
|
+
GATE_FAILED,
|
|
44479
|
+
"verify lineage must reference a PUBLISHED publish or reconcile run",
|
|
44480
|
+
{ childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
|
|
44481
|
+
);
|
|
44482
|
+
}
|
|
44483
|
+
}
|
|
44484
|
+
if (child.command === "postverify") {
|
|
44485
|
+
if (parent.command !== "verify" || parent.status !== "VERIFIED") {
|
|
44486
|
+
throw new ReleaseError(
|
|
44487
|
+
GATE_FAILED,
|
|
44488
|
+
"postverify lineage must reference a VERIFIED verify run",
|
|
44489
|
+
{ childCommand: child.command, parentCommand: parent.command, parentStatus: parent.status }
|
|
44490
|
+
);
|
|
44491
|
+
}
|
|
44492
|
+
}
|
|
44493
|
+
}
|
|
44494
|
+
async function validateRunLineage(run6, options = {}) {
|
|
44495
|
+
const { plan, planPath, runPath, production = false, maxDepth = 16 } = options;
|
|
44496
|
+
if (maxDepth < 0) {
|
|
44497
|
+
throw new ReleaseError(GATE_FAILED, "release run lineage exceeds maximum depth");
|
|
44498
|
+
}
|
|
44499
|
+
validateRun(run6, { requireDigest: production });
|
|
44500
|
+
if (production) assertImmutableRunAuthority(runPath, planPath, run6);
|
|
44501
|
+
await validateStatePredecessorChain(run6, runPath, { production, planPath });
|
|
44502
|
+
validateRunPlanDigest(run6, plan, { planPath });
|
|
44503
|
+
if (run6.command === "publish") return;
|
|
44504
|
+
if (!["reconcile", "verify", "postverify"].includes(run6.command)) {
|
|
44505
|
+
throw new ReleaseError(GATE_FAILED, `unsupported run command in lineage: ${run6.command}`);
|
|
44506
|
+
}
|
|
44507
|
+
if (!run6.sourceRunPath || !run6.sourceRunId || !run6.sourceRunDigest) {
|
|
44508
|
+
throw new ReleaseError(GATE_FAILED, `${run6.command} run is missing complete source run lineage`);
|
|
44509
|
+
}
|
|
44510
|
+
const parent = await loadRun(run6.sourceRunPath, {
|
|
44511
|
+
requireDigest: true,
|
|
44512
|
+
...production ? { authorityPlanPath: planPath } : {}
|
|
44513
|
+
});
|
|
44514
|
+
if (parent.runId !== run6.sourceRunId || parent.runDigest !== run6.sourceRunDigest) {
|
|
44515
|
+
throw new ReleaseError(
|
|
44516
|
+
GATE_FAILED,
|
|
44517
|
+
"source run lineage id/digest does not match referenced immutable run bytes",
|
|
44518
|
+
{ sourceRunPath: run6.sourceRunPath }
|
|
44519
|
+
);
|
|
44520
|
+
}
|
|
44521
|
+
validateSourceRunEdge(run6, parent);
|
|
44522
|
+
await validateRunLineage(parent, {
|
|
44523
|
+
plan,
|
|
44524
|
+
planPath,
|
|
44525
|
+
runPath: run6.sourceRunPath,
|
|
44526
|
+
production,
|
|
44527
|
+
maxDepth: maxDepth - 1
|
|
44528
|
+
});
|
|
44529
|
+
}
|
|
44530
|
+
function validateRunPlanDigest(run6, plan, options = {}) {
|
|
44531
|
+
const requirePresence = options.requirePresence !== false;
|
|
44532
|
+
const expectedDigest = computePlanDigest(plan);
|
|
44533
|
+
if (options.planPath) assertImmutablePlanAuthority(options.planPath, plan);
|
|
44534
|
+
if (!run6.planDigest) {
|
|
44535
|
+
if (requirePresence) {
|
|
44536
|
+
throw new ReleaseError(
|
|
44537
|
+
GATE_FAILED,
|
|
44538
|
+
"source run is missing required planDigest field",
|
|
44539
|
+
{ runId: run6.runId }
|
|
44540
|
+
);
|
|
44541
|
+
}
|
|
44542
|
+
return;
|
|
44543
|
+
}
|
|
44544
|
+
if (run6.planDigest !== expectedDigest) {
|
|
44545
|
+
throw new ReleaseError(
|
|
44546
|
+
GATE_FAILED,
|
|
44547
|
+
`run planDigest mismatch: run says ${String(run6.planDigest).slice(0, 16)}..., plan is ${expectedDigest.slice(0, 16)}...`,
|
|
44548
|
+
{ runDigest: run6.planDigest, planDigest: expectedDigest }
|
|
44549
|
+
);
|
|
44550
|
+
}
|
|
44551
|
+
if (plan.production && run6.planPath && options.planPath && resolve9(run6.planPath) !== resolve9(options.planPath)) {
|
|
44552
|
+
throw new ReleaseError(
|
|
44553
|
+
GATE_FAILED,
|
|
44554
|
+
"source run immutable plan path does not match the supplied plan authority",
|
|
44555
|
+
{ runPlanPath: run6.planPath, suppliedPlanPath: options.planPath }
|
|
44556
|
+
);
|
|
44557
|
+
}
|
|
44558
|
+
}
|
|
44559
|
+
function validateRunCheckpointMapping(run6, planActions) {
|
|
44560
|
+
const seenPlanActionIds = /* @__PURE__ */ new Set();
|
|
44561
|
+
for (const action of planActions) {
|
|
44562
|
+
if (seenPlanActionIds.has(action.id)) {
|
|
44563
|
+
throw new ReleaseError(
|
|
44564
|
+
GATE_FAILED,
|
|
44565
|
+
`release plan has duplicate action id "${action.id}"`,
|
|
44566
|
+
{ actionId: action.id }
|
|
44567
|
+
);
|
|
44568
|
+
}
|
|
44569
|
+
seenPlanActionIds.add(action.id);
|
|
44570
|
+
}
|
|
44571
|
+
const planActionIds = new Set(planActions.map((a) => a.id));
|
|
44572
|
+
const planActionsById = new Map(planActions.map((action) => [action.id, action]));
|
|
44573
|
+
const seenCheckpointIds = /* @__PURE__ */ new Set();
|
|
44574
|
+
for (const cp4 of run6.checkpoints) {
|
|
44575
|
+
if (seenCheckpointIds.has(cp4.actionId)) {
|
|
44576
|
+
throw new ReleaseError(
|
|
44577
|
+
GATE_FAILED,
|
|
44578
|
+
`source run has duplicate checkpoint for action "${cp4.actionId}"`,
|
|
44579
|
+
{ actionId: cp4.actionId }
|
|
44580
|
+
);
|
|
44581
|
+
}
|
|
44582
|
+
seenCheckpointIds.add(cp4.actionId);
|
|
44583
|
+
}
|
|
44584
|
+
const runCheckpointIds = new Set(run6.checkpoints.map((cp4) => cp4.actionId));
|
|
44585
|
+
for (const action of planActions) {
|
|
44586
|
+
if (!runCheckpointIds.has(action.id)) {
|
|
44587
|
+
throw new ReleaseError(
|
|
44588
|
+
GATE_FAILED,
|
|
44589
|
+
`source run missing checkpoint for plan action "${action.id}"`,
|
|
44590
|
+
{ actionId: action.id }
|
|
44591
|
+
);
|
|
44592
|
+
}
|
|
44593
|
+
}
|
|
44594
|
+
for (const cp4 of run6.checkpoints) {
|
|
44595
|
+
if (!planActionIds.has(cp4.actionId)) {
|
|
44596
|
+
throw new ReleaseError(
|
|
44597
|
+
GATE_FAILED,
|
|
44598
|
+
`source run has checkpoint for unknown action "${cp4.actionId}"`,
|
|
44599
|
+
{ actionId: cp4.actionId }
|
|
44600
|
+
);
|
|
44601
|
+
}
|
|
44602
|
+
const action = planActionsById.get(cp4.actionId);
|
|
44603
|
+
if (cp4.actionType !== action.type) {
|
|
44604
|
+
throw new ReleaseError(
|
|
44605
|
+
GATE_FAILED,
|
|
44606
|
+
`source run checkpoint actionType mismatch for action "${cp4.actionId}": run says "${cp4.actionType}", plan says "${action.type}"`,
|
|
44607
|
+
{ actionId: cp4.actionId, runActionType: cp4.actionType, planActionType: action.type }
|
|
44608
|
+
);
|
|
44609
|
+
}
|
|
44610
|
+
}
|
|
44611
|
+
}
|
|
44612
|
+
function computeRunDigest(run6) {
|
|
44613
|
+
const { runDigest: _, ...rest } = run6;
|
|
44614
|
+
return sha256Hex(JSON.stringify(rest, null, 2));
|
|
44615
|
+
}
|
|
44616
|
+
function sealRun(run6) {
|
|
44617
|
+
const sealed = { ...run6 };
|
|
44618
|
+
sealed.runDigest = computeRunDigest(sealed);
|
|
44619
|
+
validateRun(sealed, { requireDigest: true });
|
|
44620
|
+
return sealed;
|
|
44621
|
+
}
|
|
44622
|
+
async function writeRunAtomic(runPath, run6) {
|
|
44623
|
+
const sealed = sealRun(run6);
|
|
44624
|
+
const json = JSON.stringify(sealed, null, 2);
|
|
44625
|
+
const dir = dirname5(runPath);
|
|
44626
|
+
await mkdir10(dir, { recursive: true });
|
|
44627
|
+
try {
|
|
44628
|
+
await publishFileExclusive(dir, basename5(runPath), json, { mode: 384 });
|
|
44629
|
+
} catch (cause) {
|
|
44630
|
+
if (cause?.details?.kind === HARNESS_ERROR_KINDS.EXCLUSIVE_PUBLISH_CONFLICT) {
|
|
44631
|
+
const existing = await readFile19(runPath, "utf8").catch(() => null);
|
|
44632
|
+
if (existing === json) return Object.freeze(sealed);
|
|
44633
|
+
throw new ReleaseError(
|
|
44634
|
+
GATE_FAILED,
|
|
44635
|
+
"run file already exists with different bytes; exclusive-create rejected overwrite",
|
|
44636
|
+
{ runPath }
|
|
44637
|
+
);
|
|
44638
|
+
}
|
|
44639
|
+
throw new ReleaseError(
|
|
44640
|
+
GATE_FAILED,
|
|
44641
|
+
`run file write failed: ${cause?.message ?? String(cause)}`,
|
|
44642
|
+
{ runPath }
|
|
44643
|
+
);
|
|
44644
|
+
}
|
|
44645
|
+
return Object.freeze(sealed);
|
|
44646
|
+
}
|
|
44647
|
+
async function appendRunState(runDir, sequence, run6) {
|
|
44648
|
+
if (!Number.isSafeInteger(sequence) || sequence < 0) {
|
|
44649
|
+
throw new ReleaseError(GATE_FAILED, "run state sequence must be a non-negative integer");
|
|
44650
|
+
}
|
|
44651
|
+
const statesDir = join8(runDir, "states");
|
|
44652
|
+
let previousStateDigest;
|
|
44653
|
+
if (sequence > 0) {
|
|
44654
|
+
const previousPath = join8(statesDir, `${String(sequence - 1).padStart(6, "0")}.json`);
|
|
44655
|
+
const previous = await loadRun(previousPath, { requireDigest: true });
|
|
44656
|
+
if (previous.stateSequence !== sequence - 1 || previous.runId !== run6.runId || previous.command !== run6.command || previous.planDigest !== run6.planDigest) {
|
|
44657
|
+
throw new ReleaseError(
|
|
44658
|
+
GATE_FAILED,
|
|
44659
|
+
"run state predecessor does not belong to the same monotonic authority chain",
|
|
44660
|
+
{ sequence, previousPath }
|
|
44661
|
+
);
|
|
44662
|
+
}
|
|
44663
|
+
previousStateDigest = previous.runDigest;
|
|
44664
|
+
}
|
|
44665
|
+
const { previousStateDigest: _discarded, ...nextRun } = run6;
|
|
44666
|
+
const state = sealRun({
|
|
44667
|
+
...nextRun,
|
|
44668
|
+
stateSequence: sequence,
|
|
44669
|
+
...previousStateDigest ? { previousStateDigest } : {}
|
|
44670
|
+
});
|
|
44671
|
+
const statePath = join8(statesDir, `${String(sequence).padStart(6, "0")}.json`);
|
|
44672
|
+
await writeRunAtomic(statePath, state);
|
|
44673
|
+
return Object.freeze({ state, statePath });
|
|
44674
|
+
}
|
|
44675
|
+
var import_ajv5, import_ajv_formats3, RELEASE_RUN_SCHEMA, ajv2, validateRunSchema;
|
|
44676
|
+
var init_run = __esm({
|
|
44677
|
+
async "src/core/run.mjs"() {
|
|
44678
|
+
import_ajv5 = __toESM(require_ajv(), 1);
|
|
44679
|
+
import_ajv_formats3 = __toESM(require_dist3(), 1);
|
|
44680
|
+
await init_plan();
|
|
44681
|
+
init_digest();
|
|
44682
|
+
init_errors3();
|
|
44683
|
+
init_trusted_resource();
|
|
44684
|
+
init_foundation_inflight();
|
|
44685
|
+
RELEASE_RUN_SCHEMA = JSON.parse((await readTrustedPackageResource(
|
|
44686
|
+
"schemas/release-run.schema.json"
|
|
44687
|
+
)).toString("utf8"));
|
|
44688
|
+
__name(resolveDefaultRunDir, "resolveDefaultRunDir");
|
|
44689
|
+
ajv2 = new import_ajv5.default({ allErrors: true, strict: false });
|
|
44690
|
+
(0, import_ajv_formats3.default)(ajv2);
|
|
44691
|
+
validateRunSchema = ajv2.compile(RELEASE_RUN_SCHEMA);
|
|
44692
|
+
__name(validateRun, "validateRun");
|
|
44693
|
+
__name(resolveRunPath, "resolveRunPath");
|
|
44694
|
+
__name(loadRun, "loadRun");
|
|
44695
|
+
__name(assertImmutableRunAuthority, "assertImmutableRunAuthority");
|
|
44696
|
+
__name(createProductionRunDir, "createProductionRunDir");
|
|
44697
|
+
__name(createProductionPrepareRunDir, "createProductionPrepareRunDir");
|
|
44698
|
+
__name(createProductionRunDirWithinAuthority, "createProductionRunDirWithinAuthority");
|
|
44699
|
+
__name(validateStatePredecessorChain, "validateStatePredecessorChain");
|
|
44700
|
+
__name(validateSourceRunEdge, "validateSourceRunEdge");
|
|
44701
|
+
__name(validateRunLineage, "validateRunLineage");
|
|
44702
|
+
__name(validateRunPlanDigest, "validateRunPlanDigest");
|
|
44703
|
+
__name(validateRunCheckpointMapping, "validateRunCheckpointMapping");
|
|
44704
|
+
__name(computeRunDigest, "computeRunDigest");
|
|
44705
|
+
__name(sealRun, "sealRun");
|
|
44706
|
+
__name(writeRunAtomic, "writeRunAtomic");
|
|
44707
|
+
__name(appendRunState, "appendRunState");
|
|
44708
|
+
}
|
|
44709
|
+
});
|
|
44710
|
+
|
|
44711
|
+
// src/commands/verify-records.mjs
|
|
44712
|
+
var verify_records_exports = {};
|
|
44713
|
+
__export(verify_records_exports, {
|
|
44714
|
+
VERIFY_RECORDS_EXIT_CODES: () => VERIFY_RECORDS_EXIT_CODES,
|
|
44715
|
+
verifyReleaseRecords: () => verifyReleaseRecords
|
|
44716
|
+
});
|
|
44717
|
+
function sourceLabel(source) {
|
|
44718
|
+
if (typeof source !== "string" || source.length === 0) return null;
|
|
44719
|
+
return source.replaceAll("\\", "/").split("/").filter(Boolean).at(-1) ?? null;
|
|
44720
|
+
}
|
|
44721
|
+
function parseInput(input, role, addFinding) {
|
|
44722
|
+
const summary = { role, source: sourceLabel(input?.source) };
|
|
44723
|
+
if (!input || input.bytes === void 0 || input.bytes === null) {
|
|
44724
|
+
addFinding("INPUT_MISSING", role, `required ${role} input was not provided`, "INSUFFICIENT");
|
|
44725
|
+
return { summary, digest: null, document: null, bytes: null, usable: false };
|
|
44726
|
+
}
|
|
44727
|
+
let bytes;
|
|
44728
|
+
try {
|
|
44729
|
+
bytes = Buffer.isBuffer(input.bytes) ? Buffer.from(input.bytes) : Buffer.from(input.bytes);
|
|
44730
|
+
} catch {
|
|
44731
|
+
addFinding("INPUT_DAMAGED", role, `${role} input is not byte-compatible`);
|
|
44732
|
+
return { summary, digest: null, document: null, bytes: null, usable: false };
|
|
44733
|
+
}
|
|
44734
|
+
const bytesSha256 = digestBytes(bytes);
|
|
44735
|
+
try {
|
|
44736
|
+
const document2 = JSON.parse(bytes.toString("utf8"));
|
|
44737
|
+
if (!document2 || typeof document2 !== "object" || Array.isArray(document2)) {
|
|
44738
|
+
addFinding("INPUT_DAMAGED", role, `${role} input must be a JSON object`);
|
|
44739
|
+
return {
|
|
44740
|
+
summary,
|
|
44741
|
+
digest: { bytesSha256, carriedDomainDigest: null, recomputedDomainDigest: null },
|
|
44742
|
+
document: null,
|
|
44743
|
+
bytes,
|
|
44744
|
+
usable: false
|
|
44745
|
+
};
|
|
44746
|
+
}
|
|
44747
|
+
return {
|
|
44748
|
+
summary,
|
|
44749
|
+
digest: { bytesSha256, carriedDomainDigest: null, recomputedDomainDigest: null },
|
|
44750
|
+
document: document2,
|
|
44751
|
+
bytes,
|
|
44752
|
+
usable: true
|
|
44753
|
+
};
|
|
44754
|
+
} catch {
|
|
44755
|
+
addFinding("INPUT_DAMAGED", role, `${role} input is not valid JSON`);
|
|
44756
|
+
return {
|
|
44757
|
+
summary,
|
|
44758
|
+
digest: { bytesSha256, carriedDomainDigest: null, recomputedDomainDigest: null },
|
|
44759
|
+
document: null,
|
|
44760
|
+
bytes,
|
|
44761
|
+
usable: false
|
|
44762
|
+
};
|
|
44763
|
+
}
|
|
44764
|
+
}
|
|
44765
|
+
function sameStringSet(left, right) {
|
|
44766
|
+
return JSON.stringify([...new Set(left)].sort()) === JSON.stringify([...new Set(right)].sort());
|
|
44767
|
+
}
|
|
44768
|
+
function commandStatusIsValid(run6) {
|
|
44769
|
+
if (run6.command === "publish" || run6.command === "reconcile") {
|
|
44770
|
+
return run6.status === "PARTIAL" || run6.status === "PUBLISHED";
|
|
44771
|
+
}
|
|
44772
|
+
return run6.command === "verify" && run6.status === "VERIFIED";
|
|
44773
|
+
}
|
|
44774
|
+
function checkpointStatusIsValid(run6) {
|
|
44775
|
+
if (run6.status === "VERIFIED") {
|
|
44776
|
+
return run6.checkpoints.every((checkpoint) => ["succeeded", "skipped"].includes(checkpoint.status));
|
|
44777
|
+
}
|
|
44778
|
+
if (run6.status === "PUBLISHED") {
|
|
44779
|
+
return run6.checkpoints.every((checkpoint) => isRemoteWriteAction(checkpoint.actionType) ? ["succeeded", "skipped"].includes(checkpoint.status) : isMarketplaceAction(checkpoint.actionType) ? ["succeeded", "skipped", "deferred", "failed"].includes(checkpoint.status) : true);
|
|
44780
|
+
}
|
|
44781
|
+
if (run6.status === "PARTIAL") {
|
|
44782
|
+
return run6.checkpoints.some((checkpoint) => !["succeeded", "skipped", "deferred"].includes(checkpoint.status));
|
|
44783
|
+
}
|
|
44784
|
+
return false;
|
|
44785
|
+
}
|
|
44786
|
+
function verifyReleaseRecords(request = {}) {
|
|
44787
|
+
const findings = [];
|
|
44788
|
+
const addFinding = /* @__PURE__ */ __name((code, role, message, outcome = "CONTRADICTED") => {
|
|
44789
|
+
findings.push({ code, role, message, _outcome: outcome });
|
|
44790
|
+
}, "addFinding");
|
|
44791
|
+
const planInput = parseInput(request.plan, "plan", addFinding);
|
|
44792
|
+
const approvalInput = parseInput(request.approval, "approval", addFinding);
|
|
44793
|
+
const targetInput = parseInput(request.targetRun, "targetRun", addFinding);
|
|
44794
|
+
const sourceInputs = Array.isArray(request.sourceRuns) ? request.sourceRuns.map((input, index) => parseInput(input, `sourceRun[${index}]`, addFinding)) : [];
|
|
44795
|
+
const plan = planInput.document;
|
|
44796
|
+
const approval = approvalInput.document;
|
|
44797
|
+
const targetRun = targetInput.document;
|
|
44798
|
+
let planSupported = false;
|
|
44799
|
+
if (planInput.usable) {
|
|
44800
|
+
if (!SUPPORTED_PLAN_VERSIONS.has(plan?.planVersion)) {
|
|
44801
|
+
addFinding("FORMAT_UNSUPPORTED", "plan", `unsupported release plan version: ${String(plan?.planVersion)}`, "INSUFFICIENT");
|
|
44802
|
+
} else {
|
|
44803
|
+
planSupported = true;
|
|
44804
|
+
try {
|
|
44805
|
+
validatePlan(plan);
|
|
44806
|
+
} catch (error) {
|
|
44807
|
+
addFinding("INPUT_DAMAGED", "plan", error.message);
|
|
44808
|
+
planSupported = false;
|
|
44809
|
+
}
|
|
44810
|
+
const recomputed = computePlanDigest(plan);
|
|
44811
|
+
planInput.digest.carriedDomainDigest = plan?.digest ?? null;
|
|
44812
|
+
planInput.digest.recomputedDomainDigest = recomputed;
|
|
44813
|
+
if (!plan?.digest || plan.digest !== recomputed) {
|
|
44814
|
+
addFinding("PLAN_DIGEST_MISMATCH", "plan", "plan digest does not match its binding content");
|
|
44815
|
+
}
|
|
44816
|
+
}
|
|
44817
|
+
}
|
|
44818
|
+
let approvalSupported = false;
|
|
44819
|
+
let approvalDigest = null;
|
|
44820
|
+
if (approvalInput.usable) {
|
|
44821
|
+
approvalDigest = computeApprovalDigest(approvalInput.bytes);
|
|
44822
|
+
approvalInput.digest.recomputedDomainDigest = approvalDigest;
|
|
44823
|
+
try {
|
|
44824
|
+
validateApprovalRecordSchema(approval);
|
|
44825
|
+
approvalSupported = true;
|
|
44826
|
+
} catch (error) {
|
|
44827
|
+
addFinding("INPUT_DAMAGED", "approval", error.message);
|
|
44828
|
+
}
|
|
44829
|
+
if (approvalSupported) {
|
|
44830
|
+
try {
|
|
44831
|
+
validateApprovalTimeWindow(approval, {
|
|
44832
|
+
clock: /* @__PURE__ */ __name(() => approval.approvedAt, "clock"),
|
|
44833
|
+
requireUnexpired: false
|
|
44834
|
+
});
|
|
44835
|
+
} catch (error) {
|
|
44836
|
+
addFinding("INPUT_DAMAGED", "approval", error.message);
|
|
44837
|
+
approvalSupported = false;
|
|
44838
|
+
}
|
|
44839
|
+
}
|
|
44840
|
+
}
|
|
44841
|
+
const runEntries = [
|
|
44842
|
+
{ input: targetInput, role: "targetRun", isTarget: true },
|
|
44843
|
+
...sourceInputs.map((input, index) => ({ input, role: `sourceRun[${index}]`, isTarget: false }))
|
|
44844
|
+
];
|
|
44845
|
+
const usableRuns = [];
|
|
44846
|
+
let trustedTargetTerminalStatus = null;
|
|
44847
|
+
for (const entry of runEntries) {
|
|
44848
|
+
const run6 = entry.input.document;
|
|
44849
|
+
if (!entry.input.usable) continue;
|
|
44850
|
+
entry.input.digest.carriedDomainDigest = run6?.runDigest ?? null;
|
|
44851
|
+
entry.input.digest.recomputedDomainDigest = computeRunDigest(run6);
|
|
44852
|
+
if (!SUPPORTED_RUN_COMMANDS.has(run6?.command) || !SUPPORTED_TERMINAL_STATUSES.has(run6?.status)) {
|
|
44853
|
+
addFinding(
|
|
44854
|
+
"FORMAT_UNSUPPORTED",
|
|
44855
|
+
entry.role,
|
|
44856
|
+
`unsupported release run command/status: ${String(run6?.command)}/${String(run6?.status)}`,
|
|
44857
|
+
"INSUFFICIENT"
|
|
44858
|
+
);
|
|
44859
|
+
continue;
|
|
44860
|
+
}
|
|
44861
|
+
const { runDigest: _runDigest, ...unsignedRun } = run6;
|
|
44862
|
+
try {
|
|
44863
|
+
validateRun(unsignedRun);
|
|
44864
|
+
} catch (error) {
|
|
44865
|
+
addFinding("INPUT_DAMAGED", entry.role, error.message);
|
|
44866
|
+
continue;
|
|
44867
|
+
}
|
|
44868
|
+
const digestMatches = Boolean(run6.runDigest) && run6.runDigest === entry.input.digest.recomputedDomainDigest;
|
|
44869
|
+
const transitionIsValid = commandStatusIsValid(run6);
|
|
44870
|
+
const terminalCheckpointsAreValid = checkpointStatusIsValid(run6);
|
|
44871
|
+
if (!digestMatches) {
|
|
44872
|
+
addFinding("RUN_DIGEST_MISMATCH", entry.role, "run digest does not match its content");
|
|
44873
|
+
}
|
|
44874
|
+
if (!transitionIsValid) {
|
|
44875
|
+
addFinding("TRANSITION_MISMATCH", entry.role, `run command ${run6.command} cannot terminate as ${run6.status}`);
|
|
44876
|
+
}
|
|
44877
|
+
if (!terminalCheckpointsAreValid) {
|
|
44878
|
+
addFinding("CHECKPOINT_MISMATCH", entry.role, `checkpoint results do not support terminal status ${run6.status}`);
|
|
44879
|
+
}
|
|
44880
|
+
if (entry.isTarget && digestMatches && transitionIsValid && terminalCheckpointsAreValid) {
|
|
44881
|
+
trustedTargetTerminalStatus = run6.status;
|
|
44882
|
+
}
|
|
44883
|
+
usableRuns.push({ ...entry, run: run6 });
|
|
44884
|
+
}
|
|
44885
|
+
if (planSupported && typeof request.unitId === "string" && typeof request.targetVersion === "string") {
|
|
44886
|
+
const expectedUnit = (plan.units ?? []).find((unit) => unit.id === request.unitId);
|
|
44887
|
+
if (!expectedUnit || expectedUnit.targetVersion !== request.targetVersion) {
|
|
44888
|
+
addFinding("IDENTITY_MISMATCH", "plan", "expected release unit and version do not match the plan");
|
|
44889
|
+
}
|
|
44890
|
+
} else if (typeof request.unitId !== "string" || typeof request.targetVersion !== "string") {
|
|
44891
|
+
addFinding("INPUT_MISSING", "identity", "unitId and targetVersion are required", "INSUFFICIENT");
|
|
44892
|
+
}
|
|
44893
|
+
if (planSupported && approvalSupported) {
|
|
44894
|
+
const planDigest = computePlanDigest(plan);
|
|
44895
|
+
if (approval.planDigest !== planDigest) {
|
|
44896
|
+
addFinding("PLAN_BINDING_MISMATCH", "approval", "approval planDigest does not match the supplied plan");
|
|
44897
|
+
}
|
|
44898
|
+
const expectedUnitVersions = Object.fromEntries((plan.units ?? []).map((unit) => [unit.id, unit.targetVersion]));
|
|
44899
|
+
const approvedUnitVersions = approval.unitVersions ?? ((plan.units ?? []).length === 1 ? { [plan.units[0].id]: approval.targetVersion } : {});
|
|
44900
|
+
const identityMatches = sameStringSet(Object.keys(expectedUnitVersions), Object.keys(approvedUnitVersions)) && Object.entries(expectedUnitVersions).every(([unitId, version]) => approvedUnitVersions[unitId] === version) && (approval.targetVersion === void 0 || new Set(Object.values(expectedUnitVersions)).size !== 1 || approval.targetVersion === Object.values(expectedUnitVersions)[0]);
|
|
44901
|
+
if (!identityMatches) {
|
|
44902
|
+
addFinding("IDENTITY_MISMATCH", "approval", "approval unit/version identity does not match the plan");
|
|
44903
|
+
}
|
|
44904
|
+
const planActions = (plan.externalActions ?? []).map((action) => action.id);
|
|
44905
|
+
if (!sameStringSet(approval.approvedActions ?? [], planActions)) {
|
|
44906
|
+
addFinding("APPROVAL_ACTION_MISMATCH", "approval", "approved actions do not exactly match the plan actions");
|
|
44907
|
+
}
|
|
44908
|
+
try {
|
|
44909
|
+
validateApproval(plan, approval, {
|
|
44910
|
+
clock: /* @__PURE__ */ __name(() => approval.approvedAt, "clock"),
|
|
44911
|
+
requireUnexpired: false
|
|
44912
|
+
});
|
|
44913
|
+
} catch (error) {
|
|
44914
|
+
const alreadyRepresented = /planDigest/i.test(error.message) && findings.some((finding) => finding.role === "approval" && finding.code === "PLAN_BINDING_MISMATCH") || /version|unitVersions/i.test(error.message) && findings.some((finding) => finding.role === "approval" && finding.code === "IDENTITY_MISMATCH") || /approved|action/i.test(error.message) && findings.some((finding) => finding.role === "approval" && finding.code === "APPROVAL_ACTION_MISMATCH");
|
|
44915
|
+
if (!alreadyRepresented) {
|
|
44916
|
+
addFinding("INPUT_DAMAGED", "approval", error.message);
|
|
44917
|
+
}
|
|
44918
|
+
}
|
|
44919
|
+
}
|
|
44920
|
+
if (planSupported) {
|
|
44921
|
+
for (const entry of usableRuns) {
|
|
44922
|
+
try {
|
|
44923
|
+
validateRunPlanDigest(entry.run, plan);
|
|
44924
|
+
} catch (error) {
|
|
44925
|
+
addFinding("PLAN_BINDING_MISMATCH", entry.role, error.message);
|
|
44926
|
+
}
|
|
44927
|
+
try {
|
|
44928
|
+
validateRunCheckpointMapping(entry.run, plan.externalActions ?? []);
|
|
44929
|
+
} catch (error) {
|
|
44930
|
+
addFinding("CHECKPOINT_MISMATCH", entry.role, error.message);
|
|
44931
|
+
}
|
|
44932
|
+
}
|
|
44933
|
+
}
|
|
44934
|
+
if (approvalDigest) {
|
|
44935
|
+
for (const entry of usableRuns) {
|
|
44936
|
+
if (!entry.run.approvalDigest) {
|
|
44937
|
+
addFinding("INPUT_MISSING", entry.role, "run does not carry the approval digest needed to bind the supplied approval", "INSUFFICIENT");
|
|
44938
|
+
} else if (entry.run.approvalDigest !== approvalDigest) {
|
|
44939
|
+
addFinding("APPROVAL_DIGEST_MISMATCH", entry.role, "run approvalDigest does not match the supplied approval bytes");
|
|
44940
|
+
}
|
|
44941
|
+
}
|
|
44942
|
+
}
|
|
44943
|
+
const supportingRuns = usableRuns.filter((entry) => !entry.isTarget);
|
|
44944
|
+
const chain = [];
|
|
44945
|
+
let lineageComplete = false;
|
|
44946
|
+
if (targetRun && usableRuns.some((entry) => entry.isTarget)) {
|
|
44947
|
+
let current = usableRuns.find((entry) => entry.isTarget);
|
|
44948
|
+
const visited = /* @__PURE__ */ new Set();
|
|
44949
|
+
for (let depth = 0; depth <= 16 && current; depth += 1) {
|
|
44950
|
+
chain.push(current.run);
|
|
44951
|
+
const visitKey = `${current.run.runId}:${current.run.runDigest}`;
|
|
44952
|
+
if (visited.has(visitKey)) {
|
|
44953
|
+
addFinding("RUN_LINEAGE_MISMATCH", current.role, "release run lineage contains a cycle");
|
|
44954
|
+
break;
|
|
44955
|
+
}
|
|
44956
|
+
visited.add(visitKey);
|
|
44957
|
+
if (current.run.command === "publish") {
|
|
44958
|
+
lineageComplete = true;
|
|
44959
|
+
break;
|
|
44960
|
+
}
|
|
44961
|
+
if (!current.run.sourceRunId || !current.run.sourceRunDigest || !current.run.sourceRunPath) {
|
|
44962
|
+
addFinding("INPUT_MISSING", current.role, "run is missing complete source-run lineage fields", "INSUFFICIENT");
|
|
44963
|
+
break;
|
|
44964
|
+
}
|
|
44965
|
+
const exactParent = supportingRuns.find((candidate) => candidate.run.runId === current.run.sourceRunId && candidate.run.runDigest === current.run.sourceRunDigest);
|
|
44966
|
+
if (!exactParent) {
|
|
44967
|
+
const sameId = supportingRuns.find((candidate) => candidate.run.runId === current.run.sourceRunId);
|
|
44968
|
+
if (sameId) {
|
|
44969
|
+
addFinding("RUN_LINEAGE_MISMATCH", current.role, "source run id is present but its digest does not match");
|
|
44970
|
+
} else {
|
|
44971
|
+
addFinding("INPUT_MISSING", current.role, "explicit source run required by lineage was not supplied", "INSUFFICIENT");
|
|
44972
|
+
}
|
|
44973
|
+
break;
|
|
44974
|
+
}
|
|
44975
|
+
try {
|
|
44976
|
+
validateSourceRunEdge(current.run, exactParent.run);
|
|
44977
|
+
} catch (error) {
|
|
44978
|
+
addFinding("TRANSITION_MISMATCH", current.role, error.message);
|
|
44979
|
+
break;
|
|
44980
|
+
}
|
|
44981
|
+
current = exactParent;
|
|
44982
|
+
}
|
|
44983
|
+
if (!lineageComplete && chain.length > 16) {
|
|
44984
|
+
addFinding("RUN_LINEAGE_MISMATCH", "targetRun", "release run lineage exceeds maximum depth");
|
|
44985
|
+
}
|
|
44986
|
+
}
|
|
44987
|
+
for (const entry of usableRuns) {
|
|
44988
|
+
const run6 = entry.run;
|
|
44989
|
+
if (run6.stateSequence === void 0) {
|
|
44990
|
+
if (run6.previousStateDigest !== void 0) {
|
|
44991
|
+
addFinding("RUN_LINEAGE_MISMATCH", entry.role, "non-state run claims a previous state digest");
|
|
44992
|
+
}
|
|
44993
|
+
continue;
|
|
44994
|
+
}
|
|
44995
|
+
if (run6.stateSequence === 0) {
|
|
44996
|
+
if (run6.previousStateDigest !== void 0) {
|
|
44997
|
+
addFinding("RUN_LINEAGE_MISMATCH", entry.role, "initial state run claims a previous state digest");
|
|
44998
|
+
}
|
|
44999
|
+
continue;
|
|
45000
|
+
}
|
|
45001
|
+
const predecessor = supportingRuns.find((candidate) => candidate.run.runId === run6.runId && candidate.run.stateSequence === run6.stateSequence - 1);
|
|
45002
|
+
if (!predecessor) {
|
|
45003
|
+
addFinding("INPUT_MISSING", entry.role, "explicit predecessor state snapshot was not supplied", "INSUFFICIENT");
|
|
45004
|
+
} else if (predecessor.run.runDigest !== run6.previousStateDigest || predecessor.run.command !== run6.command || predecessor.run.planDigest !== run6.planDigest) {
|
|
45005
|
+
addFinding("RUN_LINEAGE_MISMATCH", entry.role, "state predecessor identity or digest does not match");
|
|
45006
|
+
}
|
|
45007
|
+
}
|
|
45008
|
+
if (approvalSupported) {
|
|
45009
|
+
const executionRun = [...chain].reverse().find((run6) => ["publish", "reconcile"].includes(run6.command) && (run6.checkpoints ?? []).some((checkpoint) => isRemoteWriteAction(checkpoint.actionType)));
|
|
45010
|
+
if (!lineageComplete || !executionRun?.startedAt || !executionRun?.finishedAt) {
|
|
45011
|
+
addFinding("HISTORICAL_TIME_MISSING", "approval", "historical publish execution interval cannot be proven", "INSUFFICIENT");
|
|
45012
|
+
} else {
|
|
45013
|
+
const approvedAt = Date.parse(approval.approvedAt);
|
|
45014
|
+
const expiresAt = Date.parse(approval.expiresAt);
|
|
45015
|
+
const startedAt = Date.parse(executionRun.startedAt);
|
|
45016
|
+
const finishedAt = Date.parse(executionRun.finishedAt);
|
|
45017
|
+
if (!Number.isFinite(startedAt) || !Number.isFinite(finishedAt) || startedAt < approvedAt || finishedAt > expiresAt || finishedAt < startedAt) {
|
|
45018
|
+
addFinding("HISTORICAL_TIME_OUTSIDE_WINDOW", "approval", "publish execution interval is outside the approval window");
|
|
45019
|
+
}
|
|
45020
|
+
}
|
|
45021
|
+
}
|
|
45022
|
+
findings.sort((left, right) => (FINDING_RANK.get(left.code) ?? 999) - (FINDING_RANK.get(right.code) ?? 999) || left.role.localeCompare(right.role) || left.message.localeCompare(right.message));
|
|
45023
|
+
const hasContradiction = findings.some((finding) => finding._outcome === "CONTRADICTED");
|
|
45024
|
+
const hasInsufficient = findings.some((finding) => finding._outcome === "INSUFFICIENT");
|
|
45025
|
+
const status = hasContradiction ? "CONTRADICTED" : hasInsufficient ? "INSUFFICIENT" : "CONSISTENT";
|
|
45026
|
+
return {
|
|
45027
|
+
status,
|
|
45028
|
+
unitId: typeof request.unitId === "string" ? request.unitId : null,
|
|
45029
|
+
targetVersion: typeof request.targetVersion === "string" ? request.targetVersion : null,
|
|
45030
|
+
historicalTerminalStatus: trustedTargetTerminalStatus,
|
|
45031
|
+
inputs: {
|
|
45032
|
+
plan: planInput.summary,
|
|
45033
|
+
approval: approvalInput.summary,
|
|
45034
|
+
targetRun: targetInput.summary,
|
|
45035
|
+
sourceRuns: sourceInputs.map((input) => input.summary)
|
|
45036
|
+
},
|
|
45037
|
+
digests: {
|
|
45038
|
+
plan: planInput.digest,
|
|
45039
|
+
approval: approvalInput.digest,
|
|
45040
|
+
targetRun: targetInput.digest,
|
|
45041
|
+
sourceRuns: sourceInputs.map((input) => input.digest)
|
|
45042
|
+
},
|
|
45043
|
+
findings: findings.map(({ _outcome, ...finding }) => finding)
|
|
45044
|
+
};
|
|
45045
|
+
}
|
|
45046
|
+
var VERIFY_RECORDS_EXIT_CODES, SUPPORTED_PLAN_VERSIONS, SUPPORTED_RUN_COMMANDS, SUPPORTED_TERMINAL_STATUSES, FINDING_ORDER, FINDING_RANK;
|
|
45047
|
+
var init_verify_records = __esm({
|
|
45048
|
+
async "src/commands/verify-records.mjs"() {
|
|
45049
|
+
init_src2();
|
|
45050
|
+
await init_approval();
|
|
45051
|
+
init_checkpoints();
|
|
45052
|
+
await init_plan();
|
|
45053
|
+
await init_run();
|
|
45054
|
+
VERIFY_RECORDS_EXIT_CODES = Object.freeze({
|
|
45055
|
+
CONSISTENT: 0,
|
|
45056
|
+
CONTRADICTED: 1,
|
|
45057
|
+
INSUFFICIENT: 2
|
|
45058
|
+
});
|
|
45059
|
+
SUPPORTED_PLAN_VERSIONS = /* @__PURE__ */ new Set([1, 2, 3]);
|
|
45060
|
+
SUPPORTED_RUN_COMMANDS = /* @__PURE__ */ new Set(["publish", "reconcile", "verify"]);
|
|
45061
|
+
SUPPORTED_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["PARTIAL", "PUBLISHED", "VERIFIED"]);
|
|
45062
|
+
FINDING_ORDER = Object.freeze([
|
|
45063
|
+
"INPUT_MISSING",
|
|
45064
|
+
"INPUT_DAMAGED",
|
|
45065
|
+
"FORMAT_UNSUPPORTED",
|
|
45066
|
+
"PLAN_DIGEST_MISMATCH",
|
|
45067
|
+
"APPROVAL_DIGEST_MISMATCH",
|
|
45068
|
+
"RUN_DIGEST_MISMATCH",
|
|
45069
|
+
"IDENTITY_MISMATCH",
|
|
45070
|
+
"PLAN_BINDING_MISMATCH",
|
|
45071
|
+
"APPROVAL_ACTION_MISMATCH",
|
|
45072
|
+
"RUN_LINEAGE_MISMATCH",
|
|
45073
|
+
"TRANSITION_MISMATCH",
|
|
45074
|
+
"CHECKPOINT_MISMATCH",
|
|
45075
|
+
"HISTORICAL_TIME_MISSING",
|
|
45076
|
+
"HISTORICAL_TIME_OUTSIDE_WINDOW"
|
|
45077
|
+
]);
|
|
45078
|
+
FINDING_RANK = new Map(FINDING_ORDER.map((code, index) => [code, index]));
|
|
45079
|
+
__name(sourceLabel, "sourceLabel");
|
|
45080
|
+
__name(parseInput, "parseInput");
|
|
45081
|
+
__name(sameStringSet, "sameStringSet");
|
|
45082
|
+
__name(commandStatusIsValid, "commandStatusIsValid");
|
|
45083
|
+
__name(checkpointStatusIsValid, "checkpointStatusIsValid");
|
|
45084
|
+
__name(verifyReleaseRecords, "verifyReleaseRecords");
|
|
45085
|
+
}
|
|
45086
|
+
});
|
|
45087
|
+
|
|
44710
45088
|
// src/core/presets.mjs
|
|
44711
45089
|
var presets_exports = {};
|
|
44712
45090
|
__export(presets_exports, {
|
|
@@ -45854,7 +46232,7 @@ var init_postpublish = __esm({
|
|
|
45854
46232
|
ENV_KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
45855
46233
|
SAFE_ID_RE2 = /^[a-z0-9][a-z0-9._-]*$/;
|
|
45856
46234
|
BRANCH_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
45857
|
-
LOCAL_HOSTS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "codebuddy", "workbuddy"]);
|
|
46235
|
+
LOCAL_HOSTS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "codebuddy", "workbuddy", "qoder"]);
|
|
45858
46236
|
HUB_HOST_RE = /^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/u;
|
|
45859
46237
|
HUB_REF_RE = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]*(?:\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/u;
|
|
45860
46238
|
PAYLOAD_SOURCE_TAG_WORKTREE = "tag-worktree";
|
|
@@ -132187,17 +132565,38 @@ function hubTargets(plan) {
|
|
|
132187
132565
|
codebuddy: "Handle manually; CodeBuddy cannot pin a Hub ref in this flow.",
|
|
132188
132566
|
workbuddy: "Handle manually; WorkBuddy cannot pin a Hub ref in this flow (it follows the CodeBuddy manual boundary)."
|
|
132189
132567
|
};
|
|
132190
|
-
return local.hosts.map((host) =>
|
|
132191
|
-
|
|
132192
|
-
|
|
132193
|
-
|
|
132194
|
-
|
|
132195
|
-
|
|
132196
|
-
|
|
132197
|
-
|
|
132198
|
-
|
|
132199
|
-
|
|
132200
|
-
|
|
132568
|
+
return local.hosts.map((host) => {
|
|
132569
|
+
if (host === "qoder") {
|
|
132570
|
+
return {
|
|
132571
|
+
targetKind: "hub-backed",
|
|
132572
|
+
executionMode: "executable",
|
|
132573
|
+
unitId: declaration.unitId,
|
|
132574
|
+
host,
|
|
132575
|
+
plugin: local.plugin,
|
|
132576
|
+
marketplace: hub.name,
|
|
132577
|
+
hub,
|
|
132578
|
+
version: unit?.targetVersion,
|
|
132579
|
+
pluginRepo: unit?.publicRepo,
|
|
132580
|
+
pluginCommit: unit?.frozenSnapshot?.commit,
|
|
132581
|
+
snapshotPath: unit?.frozenSnapshot?.path,
|
|
132582
|
+
manifestDigest: unit?.frozenSnapshot?.manifestDigest,
|
|
132583
|
+
timeoutMs: 3e5,
|
|
132584
|
+
message: `Update ${local.plugin} from Qoder Hub ${hub.name}; a new session or /plugins reload is required before treating the updated plugin as loaded.`,
|
|
132585
|
+
...tag ? { frozenTag: tag } : {}
|
|
132586
|
+
};
|
|
132587
|
+
}
|
|
132588
|
+
return {
|
|
132589
|
+
targetKind: "hub-backed",
|
|
132590
|
+
executionMode: "manual",
|
|
132591
|
+
unitId: declaration.unitId,
|
|
132592
|
+
host,
|
|
132593
|
+
plugin: local.plugin,
|
|
132594
|
+
hub,
|
|
132595
|
+
message: `${manualInstruction[host]} Install or upgrade ${local.plugin} from Hub ${hub.name}; release-skill does not execute or probe this action.`,
|
|
132596
|
+
...unit?.publicRepo ? { publicRepo: unit.publicRepo } : {},
|
|
132597
|
+
...tag ? { frozenTag: tag } : {}
|
|
132598
|
+
};
|
|
132599
|
+
});
|
|
132201
132600
|
});
|
|
132202
132601
|
}
|
|
132203
132602
|
function mergePostReleaseTargets(executableTargets, manualTargets) {
|
|
@@ -132252,6 +132651,28 @@ function assertExecutableTarget(target) {
|
|
|
132252
132651
|
throw new Error(`local host update requires the frozen public commit for unit ${target.unitId}`);
|
|
132253
132652
|
}
|
|
132254
132653
|
}
|
|
132654
|
+
function assertQoderExecutableTarget(target) {
|
|
132655
|
+
for (const field of [
|
|
132656
|
+
"unitId",
|
|
132657
|
+
"plugin",
|
|
132658
|
+
"marketplace",
|
|
132659
|
+
"version",
|
|
132660
|
+
"pluginRepo",
|
|
132661
|
+
"pluginCommit",
|
|
132662
|
+
"snapshotPath",
|
|
132663
|
+
"manifestDigest"
|
|
132664
|
+
]) {
|
|
132665
|
+
if (typeof target[field] !== "string" || target[field].length === 0) {
|
|
132666
|
+
throw new Error(`Qoder local host update target is missing ${field}`);
|
|
132667
|
+
}
|
|
132668
|
+
}
|
|
132669
|
+
if (!/^[a-f0-9]{40}$/u.test(target.pluginCommit)) {
|
|
132670
|
+
throw new Error(`Qoder local host update requires the frozen public commit for unit ${target.unitId}`);
|
|
132671
|
+
}
|
|
132672
|
+
if (!/^[a-f0-9]{64}$/u.test(target.manifestDigest)) {
|
|
132673
|
+
throw new Error(`Qoder local host update requires the frozen snapshot digest for unit ${target.unitId}`);
|
|
132674
|
+
}
|
|
132675
|
+
}
|
|
132255
132676
|
function buildShipNextStep({ root, statePath, unitIds }) {
|
|
132256
132677
|
const argv = ["release-skill", "ship"];
|
|
132257
132678
|
if (typeof root === "string" && root.length > 0) argv.push("--root", root);
|
|
@@ -132277,8 +132698,12 @@ function derivePostReleaseChecklist(plan, {
|
|
|
132277
132698
|
}
|
|
132278
132699
|
const units = plan.units ?? [];
|
|
132279
132700
|
const uncovered = units.filter((unit) => !BRANCH_ACTION_INCLUDED.has(unit.productionConfig?.branchStrategy));
|
|
132280
|
-
const
|
|
132281
|
-
const
|
|
132701
|
+
const declaredHubTargets = hubTargets(plan);
|
|
132702
|
+
const executableTargets = [
|
|
132703
|
+
...pluginTargets(plan),
|
|
132704
|
+
...declaredHubTargets.filter((target) => target.executionMode === "executable")
|
|
132705
|
+
];
|
|
132706
|
+
const manualTargets = declaredHubTargets.filter((target) => target.executionMode === "manual");
|
|
132282
132707
|
const targets = mergePostReleaseTargets(executableTargets, manualTargets);
|
|
132283
132708
|
const hasPendingPostVerify = postVerifyHooks(plan).length > 0 && !postVerifyComplete && targets.length > 0;
|
|
132284
132709
|
const hasStatePath = typeof statePath === "string" && statePath.length > 0;
|
|
@@ -132642,6 +133067,184 @@ function exactPluginObservation(target, stdout) {
|
|
|
132642
133067
|
function normalizeGitSource(source) {
|
|
132643
133068
|
return String(source ?? "").replace(/^git\+/, "").replace(/^https:\/\/github\.com\//, "").replace(/^git@github\.com:/, "").replace(/\.git$/u, "").replace(/\/$/u, "");
|
|
132644
133069
|
}
|
|
133070
|
+
function normalizeHubGitSource(source, githubHost) {
|
|
133071
|
+
return String(source ?? "").replace(/^git\+/, "").replace(new RegExp(`^https://${String(githubHost).replaceAll(".", "\\.").replaceAll("-", "\\-")}/`), "").replace(new RegExp(`^git@${String(githubHost).replaceAll(".", "\\.").replaceAll("-", "\\-")}:`), "").replace(/\.git$/u, "").replace(/\/$/u, "");
|
|
133072
|
+
}
|
|
133073
|
+
async function observeQoderMarketplace(target, command2, env, run6) {
|
|
133074
|
+
const listed = await run6(command2, [...QODER_MARKETPLACE_LIST_ARGS], {
|
|
133075
|
+
env,
|
|
133076
|
+
timeout: target.timeoutMs
|
|
133077
|
+
});
|
|
133078
|
+
const parsed = parseJson2(listed.stdout, "qoder marketplace list");
|
|
133079
|
+
if (!Array.isArray(parsed)) throw new Error("qoder marketplace list did not return an array");
|
|
133080
|
+
const matches = parsed.filter((entry) => entry?.name === target.marketplace);
|
|
133081
|
+
if (matches.length === 0) return { installed: false };
|
|
133082
|
+
if (matches.length !== 1) {
|
|
133083
|
+
throw new Error(`qoder marketplace list returned conflicting entries for ${target.marketplace}`);
|
|
133084
|
+
}
|
|
133085
|
+
const [found] = matches;
|
|
133086
|
+
if (found.source?.source !== "git" || normalizeHubGitSource(found.source.url, target.hub.githubHost) !== target.hub.repo) {
|
|
133087
|
+
throw new Error(`qoder marketplace ${target.marketplace} does not point to ${target.hub.repo}`);
|
|
133088
|
+
}
|
|
133089
|
+
if (typeof found.installLocation !== "string" || found.installLocation.length === 0) {
|
|
133090
|
+
throw new Error(`qoder marketplace ${target.marketplace} has no observable checkout root`);
|
|
133091
|
+
}
|
|
133092
|
+
const [remote, branch] = await Promise.all([
|
|
133093
|
+
run6("git", ["-C", found.installLocation, "remote", "get-url", "origin"], {
|
|
133094
|
+
env,
|
|
133095
|
+
timeout: 3e4
|
|
133096
|
+
}),
|
|
133097
|
+
run6("git", ["-C", found.installLocation, "symbolic-ref", "-q", "HEAD"], {
|
|
133098
|
+
env,
|
|
133099
|
+
timeout: 3e4
|
|
133100
|
+
})
|
|
133101
|
+
]);
|
|
133102
|
+
if (normalizeHubGitSource(remote.stdout.trim(), target.hub.githubHost) !== target.hub.repo || branch.stdout.trim() !== target.hub.ref) {
|
|
133103
|
+
throw new Error(`qoder marketplace ${target.marketplace} checkout does not match the frozen Hub source`);
|
|
133104
|
+
}
|
|
133105
|
+
return { installed: true, root: found.installLocation, found };
|
|
133106
|
+
}
|
|
133107
|
+
async function observeQoderPlugin(target, command2, env, run6) {
|
|
133108
|
+
const listed = await run6(command2, [...QODER_PLUGIN_LIST_ARGS], {
|
|
133109
|
+
env,
|
|
133110
|
+
timeout: target.timeoutMs
|
|
133111
|
+
});
|
|
133112
|
+
const parsed = parseJson2(listed.stdout, "qoder plugin list");
|
|
133113
|
+
if (!Array.isArray(parsed)) throw new Error("qoder plugin list did not return an array");
|
|
133114
|
+
const selector = `${target.plugin}@${target.marketplace}`;
|
|
133115
|
+
const matches = parsed.filter((entry) => entry?.id === selector);
|
|
133116
|
+
if (matches.length === 0) return { installed: false };
|
|
133117
|
+
if (matches.length !== 1) {
|
|
133118
|
+
throw new Error(`qoder plugin list returned conflicting entries for ${selector}`);
|
|
133119
|
+
}
|
|
133120
|
+
const [found] = matches;
|
|
133121
|
+
if (found.name !== target.plugin || found.source !== selector || found.scope !== "user" || typeof found.installPath !== "string" || found.installPath.length === 0) {
|
|
133122
|
+
throw new Error(`qoder plugin ${selector} does not match its frozen user-scope identity`);
|
|
133123
|
+
}
|
|
133124
|
+
return {
|
|
133125
|
+
installed: true,
|
|
133126
|
+
exact: found.version === target.version,
|
|
133127
|
+
installPath: found.installPath,
|
|
133128
|
+
found
|
|
133129
|
+
};
|
|
133130
|
+
}
|
|
133131
|
+
async function readQoderManifest(root, label) {
|
|
133132
|
+
const manifest = parseJson2(
|
|
133133
|
+
await readFileContained(root, ".qoder-plugin/plugin.json", { encoding: "utf8" }),
|
|
133134
|
+
label
|
|
133135
|
+
);
|
|
133136
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
133137
|
+
throw new Error(`${label} did not return an object`);
|
|
133138
|
+
}
|
|
133139
|
+
return manifest;
|
|
133140
|
+
}
|
|
133141
|
+
function assertQoderPluginManifest(target, manifest, label) {
|
|
133142
|
+
if (manifest.name !== target.plugin || manifest.version !== target.version || manifest.skills !== "./adapters/qoder/skills/") {
|
|
133143
|
+
throw new Error(`${label} does not match the frozen Qoder plugin identity and projection path`);
|
|
133144
|
+
}
|
|
133145
|
+
}
|
|
133146
|
+
async function assertFrozenQoderManifest(target, root) {
|
|
133147
|
+
const { snapshotDir: snapshotRoot } = await verifyFrozenSnapshot({
|
|
133148
|
+
root,
|
|
133149
|
+
snapshotPath: target.snapshotPath,
|
|
133150
|
+
expectedDigest: target.manifestDigest
|
|
133151
|
+
});
|
|
133152
|
+
const manifest = await readQoderManifest(snapshotRoot, "frozen Qoder plugin manifest");
|
|
133153
|
+
assertQoderPluginManifest(target, manifest, "frozen Qoder plugin manifest");
|
|
133154
|
+
}
|
|
133155
|
+
async function assertQoderHubEntry(target, marketplaceRoot) {
|
|
133156
|
+
const marketplace = parseJson2(
|
|
133157
|
+
await readFileContained(marketplaceRoot, "marketplace.json", { encoding: "utf8" }),
|
|
133158
|
+
"Qoder Hub marketplace manifest"
|
|
133159
|
+
);
|
|
133160
|
+
if (marketplace?.name !== target.marketplace || !Array.isArray(marketplace.plugins)) {
|
|
133161
|
+
throw new Error("Qoder Hub marketplace manifest does not match the frozen marketplace");
|
|
133162
|
+
}
|
|
133163
|
+
const matches = marketplace.plugins.filter((entry) => entry?.name === target.plugin);
|
|
133164
|
+
if (matches.length !== 1) {
|
|
133165
|
+
throw new Error(`Qoder Hub marketplace manifest must contain exactly one ${target.plugin} entry`);
|
|
133166
|
+
}
|
|
133167
|
+
const source = matches[0]?.source;
|
|
133168
|
+
if (source?.source !== "url" || normalizeHubGitSource(source.url, target.hub.githubHost) !== target.pluginRepo || source.sha !== target.pluginCommit) {
|
|
133169
|
+
throw new Error("Qoder Hub entry does not match the frozen public source identity");
|
|
133170
|
+
}
|
|
133171
|
+
}
|
|
133172
|
+
async function verifyQoderInstalledPayload({
|
|
133173
|
+
target,
|
|
133174
|
+
root,
|
|
133175
|
+
installPath,
|
|
133176
|
+
verifyInstalledPayload
|
|
133177
|
+
}) {
|
|
133178
|
+
const manifest = await readQoderManifest(installPath, "installed Qoder plugin manifest");
|
|
133179
|
+
assertQoderPluginManifest(target, manifest, "installed Qoder plugin manifest");
|
|
133180
|
+
await verifyInstalledPayload({
|
|
133181
|
+
snapshotPath: target.snapshotPath,
|
|
133182
|
+
manifestDigest: target.manifestDigest,
|
|
133183
|
+
payloadContract: QODER_PAYLOAD_CONTRACT,
|
|
133184
|
+
marketplaceLocation: "external"
|
|
133185
|
+
}, { root }, installPath, "qoder");
|
|
133186
|
+
}
|
|
133187
|
+
async function runQoderUpdate(target, detected, run6, {
|
|
133188
|
+
root,
|
|
133189
|
+
verifyInstalledPayload
|
|
133190
|
+
}) {
|
|
133191
|
+
const env = hostEnvironment("qoder");
|
|
133192
|
+
await assertFrozenQoderManifest(target, root);
|
|
133193
|
+
const marketplace = await observeQoderMarketplace(target, detected.command, env, run6);
|
|
133194
|
+
if (!marketplace.installed) {
|
|
133195
|
+
return {
|
|
133196
|
+
status: "MANUAL_REQUIRED",
|
|
133197
|
+
reason: `qoder marketplace ${target.marketplace} is not installed; no marketplace was added`
|
|
133198
|
+
};
|
|
133199
|
+
}
|
|
133200
|
+
const before = await observeQoderPlugin(target, detected.command, env, run6);
|
|
133201
|
+
if (!before.installed) {
|
|
133202
|
+
return {
|
|
133203
|
+
status: "MANUAL_REQUIRED",
|
|
133204
|
+
reason: "qoder target plugin is not installed; no initial installation was performed"
|
|
133205
|
+
};
|
|
133206
|
+
}
|
|
133207
|
+
if (before.exact) {
|
|
133208
|
+
await assertQoderHubEntry(target, marketplace.root);
|
|
133209
|
+
await verifyQoderInstalledPayload({
|
|
133210
|
+
target,
|
|
133211
|
+
root,
|
|
133212
|
+
installPath: before.installPath,
|
|
133213
|
+
verifyInstalledPayload
|
|
133214
|
+
});
|
|
133215
|
+
return { status: "ALREADY_CURRENT", version: target.version };
|
|
133216
|
+
}
|
|
133217
|
+
await run6(detected.command, ["plugins", "marketplace", "update", target.marketplace], {
|
|
133218
|
+
env,
|
|
133219
|
+
timeout: target.timeoutMs
|
|
133220
|
+
});
|
|
133221
|
+
const refreshed = await observeQoderMarketplace(target, detected.command, env, run6);
|
|
133222
|
+
if (!refreshed.installed) {
|
|
133223
|
+
throw new Error(`qoder marketplace ${target.marketplace} disappeared after refresh`);
|
|
133224
|
+
}
|
|
133225
|
+
await assertQoderHubEntry(target, refreshed.root);
|
|
133226
|
+
await run6(detected.command, [
|
|
133227
|
+
"plugins",
|
|
133228
|
+
"update",
|
|
133229
|
+
`${target.plugin}@${target.marketplace}`,
|
|
133230
|
+
"--scope",
|
|
133231
|
+
"user"
|
|
133232
|
+
], { env, timeout: target.timeoutMs });
|
|
133233
|
+
const after = await observeQoderPlugin(target, detected.command, env, run6);
|
|
133234
|
+
if (!after.exact) throw new Error("qoder did not update to the frozen plugin version");
|
|
133235
|
+
await verifyQoderInstalledPayload({
|
|
133236
|
+
target,
|
|
133237
|
+
root,
|
|
133238
|
+
installPath: after.installPath,
|
|
133239
|
+
verifyInstalledPayload
|
|
133240
|
+
});
|
|
133241
|
+
return {
|
|
133242
|
+
status: "UPDATED",
|
|
133243
|
+
version: target.version,
|
|
133244
|
+
restartRequired: true,
|
|
133245
|
+
reloadInstruction: "Start a new Qoder session or run /plugins reload before checking the loaded version."
|
|
133246
|
+
};
|
|
133247
|
+
}
|
|
132645
133248
|
function parseFrozenRemoteRef(target, stdout) {
|
|
132646
133249
|
const directRefs = /* @__PURE__ */ new Map();
|
|
132647
133250
|
const peeledRefs = /* @__PURE__ */ new Map();
|
|
@@ -132954,14 +133557,14 @@ expect {
|
|
|
132954
133557
|
eof { failEof remove-confirmation 126 }
|
|
132955
133558
|
}
|
|
132956
133559
|
expect {
|
|
132957
|
-
-nocase -re {Trust this folder\\?} {
|
|
133560
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
132958
133561
|
-re $promptPattern {}
|
|
132959
133562
|
timeout { failTimeout remove-prompt 128 130 }
|
|
132960
133563
|
eof { failEof remove-prompt 129 }
|
|
132961
133564
|
}
|
|
132962
133565
|
}
|
|
132963
133566
|
-re $promptPattern {}
|
|
132964
|
-
-nocase -re {Trust this folder\\?} {
|
|
133567
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
132965
133568
|
timeout { failTimeout remove-dialog 122 124 }
|
|
132966
133569
|
eof { failEof remove-dialog 123 }
|
|
132967
133570
|
}
|
|
@@ -133035,9 +133638,62 @@ proc failEof {state code} {
|
|
|
133035
133638
|
exit $code
|
|
133036
133639
|
}
|
|
133037
133640
|
|
|
133038
|
-
proc
|
|
133039
|
-
puts stderr "KIMI_TUI_STATE:directory-trust:
|
|
133040
|
-
exit
|
|
133641
|
+
proc unexpectedDirectoryTrust {} {
|
|
133642
|
+
puts stderr "KIMI_TUI_STATE:directory-trust:unexpected"
|
|
133643
|
+
exit 147
|
|
133644
|
+
}
|
|
133645
|
+
|
|
133646
|
+
proc confirmInitialDirectoryTrust {} {
|
|
133647
|
+
global expect_out promptPattern
|
|
133648
|
+
set dialogBuffer $expect_out(buffer)
|
|
133649
|
+
expect {
|
|
133650
|
+
-nocase -re {\u276F[^\\r\\n]*No,[ \\t]*exit} {
|
|
133651
|
+
append dialogBuffer $expect_out(buffer)
|
|
133652
|
+
}
|
|
133653
|
+
-re {\u276F[^\\r\\n]*\\r*\\n} {
|
|
133654
|
+
puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
|
|
133655
|
+
exit 143
|
|
133656
|
+
}
|
|
133657
|
+
timeout {
|
|
133658
|
+
puts stderr "KIMI_TUI_STATE:directory-trust-selected-row:timeout"
|
|
133659
|
+
exit 140
|
|
133660
|
+
}
|
|
133661
|
+
eof { failEof directory-trust-selected-row 141 }
|
|
133662
|
+
}
|
|
133663
|
+
expect {
|
|
133664
|
+
-nocase -re {(?:^|\\r|\\n)[ \\t]+Trust this folder[ \\t]*\\r*\\n} {
|
|
133665
|
+
append dialogBuffer $expect_out(buffer)
|
|
133666
|
+
}
|
|
133667
|
+
-re {(?:^|\\r|\\n)[^\\r\\n]*\\r*\\n} {
|
|
133668
|
+
puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
|
|
133669
|
+
exit 143
|
|
133670
|
+
}
|
|
133671
|
+
timeout { failTimeout directory-trust-target-row 148 150 }
|
|
133672
|
+
eof { failEof directory-trust-target-row 149 }
|
|
133673
|
+
}
|
|
133674
|
+
set dialog [cleanScreen $dialogBuffer]
|
|
133675
|
+
if {![regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*No,[ \\t]*exit} $dialog]
|
|
133676
|
+
|| ![regexp -nocase {(^|\\n)[ \\t]+Trust this folder[ \\t]*($|\\n)} $dialog]} {
|
|
133677
|
+
puts stderr "KIMI_TUI_STATE:directory-trust:selection-unknown"
|
|
133678
|
+
exit 143
|
|
133679
|
+
}
|
|
133680
|
+
send -- "\\033\\[B"
|
|
133681
|
+
expect {
|
|
133682
|
+
-nocase -re {\u276F[^\\r\\n]*Trust this folder} {}
|
|
133683
|
+
-re {\u276F[^\\r\\n]*\\r*\\n} {
|
|
133684
|
+
puts stderr "KIMI_TUI_STATE:directory-trust-confirm-selection:unknown"
|
|
133685
|
+
exit 146
|
|
133686
|
+
}
|
|
133687
|
+
timeout { failTimeout directory-trust-confirm-selection 144 146 }
|
|
133688
|
+
eof { failEof directory-trust-confirm-selection 145 }
|
|
133689
|
+
}
|
|
133690
|
+
send -- "\\033\\[13u"
|
|
133691
|
+
expect {
|
|
133692
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
133693
|
+
-re $promptPattern {}
|
|
133694
|
+
timeout { failTimeout directory-trust-prompt 151 153 }
|
|
133695
|
+
eof { failEof directory-trust-prompt 152 }
|
|
133696
|
+
}
|
|
133041
133697
|
}
|
|
133042
133698
|
|
|
133043
133699
|
proc submitCommand {command} {
|
|
@@ -133053,7 +133709,7 @@ if {[catch {exec stty columns 240 rows 60 < $spawn_out(slave,name)} resizeError]
|
|
|
133053
133709
|
exit 131
|
|
133054
133710
|
}
|
|
133055
133711
|
expect {
|
|
133056
|
-
-nocase -re {Trust this folder\\?} {
|
|
133712
|
+
-nocase -re {Trust this folder\\?} { confirmInitialDirectoryTrust }
|
|
133057
133713
|
-re $promptPattern {}
|
|
133058
133714
|
timeout { failTimeout initial-prompt 101 103 }
|
|
133059
133715
|
eof { failEof initial-prompt 102 }
|
|
@@ -133061,7 +133717,7 @@ expect {
|
|
|
133061
133717
|
${removeCommand}submitCommand "/plugins install $installUrl"
|
|
133062
133718
|
set dialogBuffer ""
|
|
133063
133719
|
expect {
|
|
133064
|
-
-nocase -re {Trust this folder\\?} {
|
|
133720
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
133065
133721
|
-nocase -re {(?:Install third-party plugin|Trust and install from)[ \\t]} {
|
|
133066
133722
|
append dialogBuffer $expect_out(buffer)
|
|
133067
133723
|
}
|
|
@@ -133069,7 +133725,7 @@ expect {
|
|
|
133069
133725
|
eof { failEof plugin-trust-anchor 105 }
|
|
133070
133726
|
}
|
|
133071
133727
|
expect {
|
|
133072
|
-
-nocase -re {Trust this folder\\?} {
|
|
133728
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
133073
133729
|
-nocase -re {\u276F[^\\r\\n]*(?:Exit|Cancel|Trust and install)} {
|
|
133074
133730
|
append dialogBuffer $expect_out(buffer)
|
|
133075
133731
|
}
|
|
@@ -133105,7 +133761,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*trust and install} $dialog]} {
|
|
|
133105
133761
|
} elseif {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*(cancel|exit)} $dialog]} {
|
|
133106
133762
|
send -- "\\033\\[B"
|
|
133107
133763
|
expect {
|
|
133108
|
-
-nocase -re {Trust this folder\\?} {
|
|
133764
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
133109
133765
|
-nocase -re {\u276F[^\\r\\n]*Trust and install} {}
|
|
133110
133766
|
-re {\u276F[^\\r\\n]*\\r*\\n} {
|
|
133111
133767
|
puts stderr "KIMI_TUI_STATE:plugin-trust-confirm-selection:unknown"
|
|
@@ -133121,7 +133777,7 @@ if {[regexp -nocase {(^|\\n)[^\\n]*\u276F[^\\n]*trust and install} $dialog]} {
|
|
|
133121
133777
|
}
|
|
133122
133778
|
|
|
133123
133779
|
expect {
|
|
133124
|
-
-nocase -re {Trust this folder\\?} {
|
|
133780
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
133125
133781
|
-nocase -re {Install finished[^\\r\\n]*see details below\\.} {}
|
|
133126
133782
|
-nocase -re {Installing plugin from[^\\r\\n]*(?:\\r|\\n)} {
|
|
133127
133783
|
exp_continue -continue_timer
|
|
@@ -133141,14 +133797,14 @@ expect {
|
|
|
133141
133797
|
eof { failEof install-result 137 }
|
|
133142
133798
|
}
|
|
133143
133799
|
expect {
|
|
133144
|
-
-nocase -re {Trust this folder\\?} {
|
|
133800
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
133145
133801
|
-re $promptPattern {}
|
|
133146
133802
|
timeout { failTimeout post-install-prompt 114 116 }
|
|
133147
133803
|
eof { failEof post-install-prompt 115 }
|
|
133148
133804
|
}
|
|
133149
133805
|
submitCommand "/reload"
|
|
133150
133806
|
expect {
|
|
133151
|
-
-nocase -re {Trust this folder\\?} {
|
|
133807
|
+
-nocase -re {Trust this folder\\?} { unexpectedDirectoryTrust }
|
|
133152
133808
|
-re $promptPattern {}
|
|
133153
133809
|
timeout { failTimeout reload-prompt 117 119 }
|
|
133154
133810
|
eof { failEof reload-prompt 118 }
|
|
@@ -133246,29 +133902,20 @@ async function runKimiUpdate(target, detected, run6, kimiHome, {
|
|
|
133246
133902
|
}
|
|
133247
133903
|
const installUrl = `https://github.com/${target.pluginRepo}/releases/tag/${target.pluginTag}`;
|
|
133248
133904
|
const removePlugin = before.source === "legacy" ? target.plugin : "";
|
|
133249
|
-
|
|
133250
|
-
|
|
133251
|
-
|
|
133252
|
-
|
|
133253
|
-
|
|
133254
|
-
|
|
133255
|
-
|
|
133256
|
-
|
|
133257
|
-
|
|
133258
|
-
|
|
133259
|
-
|
|
133260
|
-
|
|
133261
|
-
}
|
|
133262
|
-
});
|
|
133263
|
-
} catch (error) {
|
|
133264
|
-
if (error?.exitStatus === 80) {
|
|
133265
|
-
return {
|
|
133266
|
-
status: "MANUAL_REQUIRED",
|
|
133267
|
-
reason: "Kimi requires folder trust; release-finish did not confirm the folder or continue installation"
|
|
133268
|
-
};
|
|
133905
|
+
await run6(detected.expectCommand, ["-c", kimiExpectProgram({
|
|
133906
|
+
...removePlugin ? { removePlugin } : {}
|
|
133907
|
+
})], {
|
|
133908
|
+
cwd: root,
|
|
133909
|
+
timeout: Math.max(3e5, target.timeoutMs),
|
|
133910
|
+
env: {
|
|
133911
|
+
...env,
|
|
133912
|
+
RELEASE_SKILL_KIMI_COMMAND: detected.command,
|
|
133913
|
+
RELEASE_SKILL_KIMI_INSTALL_URL: installUrl,
|
|
133914
|
+
RELEASE_SKILL_KIMI_REMOVE_PLUGIN: removePlugin,
|
|
133915
|
+
RELEASE_SKILL_KIMI_EXPECTED_REPO: `https://github.com/${target.pluginRepo}`,
|
|
133916
|
+
RELEASE_SKILL_KIMI_EXPECTED_TAG: target.pluginTag
|
|
133269
133917
|
}
|
|
133270
|
-
|
|
133271
|
-
}
|
|
133918
|
+
});
|
|
133272
133919
|
}, { prefix: "release-skill-kimi-update-" });
|
|
133273
133920
|
if (tuiOutcome) return tuiOutcome;
|
|
133274
133921
|
const after = await observeKimiTarget(target, kimiHome, run6);
|
|
@@ -133325,8 +133972,11 @@ async function updateLocalHostPluginsInternal({
|
|
|
133325
133972
|
const selected = new Set(Array.isArray(selectedHosts) ? selectedHosts : []);
|
|
133326
133973
|
const unknown = [...selected].filter((host) => !checklist.localHostUpdate.hosts.includes(host));
|
|
133327
133974
|
if (unknown.length > 0) throw new Error(`selected hosts are not declared by the plan: ${unknown.join(", ")}`);
|
|
133328
|
-
const targets =
|
|
133329
|
-
for (const target of targets)
|
|
133975
|
+
const targets = checklist.localHostUpdate.targets.filter((item) => item.executionMode !== "manual" && selected.has(item.host));
|
|
133976
|
+
for (const target of targets) {
|
|
133977
|
+
if (target.host === "qoder") assertQoderExecutableTarget(target);
|
|
133978
|
+
else assertExecutableTarget(target);
|
|
133979
|
+
}
|
|
133330
133980
|
const results = [];
|
|
133331
133981
|
for (const target of targets) {
|
|
133332
133982
|
try {
|
|
@@ -133344,6 +133994,9 @@ async function updateLocalHostPluginsInternal({
|
|
|
133344
133994
|
plan,
|
|
133345
133995
|
root,
|
|
133346
133996
|
verifyInstalledPayload
|
|
133997
|
+
}) : target.host === "qoder" ? await runQoderUpdate(target, detected, run6, {
|
|
133998
|
+
root,
|
|
133999
|
+
verifyInstalledPayload
|
|
133347
134000
|
}) : await runStructuredUpdate(target, detected, run6, {
|
|
133348
134001
|
plan,
|
|
133349
134002
|
root,
|
|
@@ -133363,7 +134016,7 @@ async function updateLocalHostPluginsInternal({
|
|
|
133363
134016
|
releaseStatusChanged: false
|
|
133364
134017
|
};
|
|
133365
134018
|
}
|
|
133366
|
-
var HOSTS_BY_ACTION, DISTRIBUTION_BY_ACTION, BRANCH_ACTION_INCLUDED, CODEBUDDY_MACOS_PATH, SAFE_ENV_KEYS, CODEBUDDY_PLUGIN_LIST_ARGS, CODEBUDDY_MARKETPLACE_LIST_ARGS;
|
|
134019
|
+
var HOSTS_BY_ACTION, DISTRIBUTION_BY_ACTION, BRANCH_ACTION_INCLUDED, CODEBUDDY_MACOS_PATH, SAFE_ENV_KEYS, CODEBUDDY_PLUGIN_LIST_ARGS, CODEBUDDY_MARKETPLACE_LIST_ARGS, QODER_PLUGIN_LIST_ARGS, QODER_MARKETPLACE_LIST_ARGS, QODER_PAYLOAD_CONTRACT;
|
|
133367
134020
|
var init_post_release_local = __esm({
|
|
133368
134021
|
async "src/commands/post-release-local.mjs"() {
|
|
133369
134022
|
init_src2();
|
|
@@ -133372,6 +134025,7 @@ var init_post_release_local = __esm({
|
|
|
133372
134025
|
await init_plugin_marketplace();
|
|
133373
134026
|
init_postpublish();
|
|
133374
134027
|
await init_run();
|
|
134028
|
+
init_frozen();
|
|
133375
134029
|
HOSTS_BY_ACTION = Object.freeze({
|
|
133376
134030
|
"claude-marketplace-install": ["claude"],
|
|
133377
134031
|
"codex-marketplace-install": ["codex"],
|
|
@@ -133412,12 +134066,16 @@ var init_post_release_local = __esm({
|
|
|
133412
134066
|
]);
|
|
133413
134067
|
CODEBUDDY_PLUGIN_LIST_ARGS = Object.freeze(["plugin", "list", "--json"]);
|
|
133414
134068
|
CODEBUDDY_MARKETPLACE_LIST_ARGS = Object.freeze(["plugin", "marketplace", "list"]);
|
|
134069
|
+
QODER_PLUGIN_LIST_ARGS = Object.freeze(["plugins", "list", "--json"]);
|
|
134070
|
+
QODER_MARKETPLACE_LIST_ARGS = Object.freeze(["plugins", "marketplace", "list", "--json"]);
|
|
134071
|
+
QODER_PAYLOAD_CONTRACT = "external-marketplace-v1";
|
|
133415
134072
|
__name(attachFoundationFailure, "attachFoundationFailure");
|
|
133416
134073
|
__name(actionTarget, "actionTarget");
|
|
133417
134074
|
__name(pluginTargets, "pluginTargets");
|
|
133418
134075
|
__name(hubTargets, "hubTargets");
|
|
133419
134076
|
__name(mergePostReleaseTargets, "mergePostReleaseTargets");
|
|
133420
134077
|
__name(assertExecutableTarget, "assertExecutableTarget");
|
|
134078
|
+
__name(assertQoderExecutableTarget, "assertQoderExecutableTarget");
|
|
133421
134079
|
__name(buildShipNextStep, "buildShipNextStep");
|
|
133422
134080
|
__name(derivePostReleaseChecklist, "derivePostReleaseChecklist");
|
|
133423
134081
|
__name(postVerifyHooks, "postVerifyHooks");
|
|
@@ -133441,6 +134099,15 @@ var init_post_release_local = __esm({
|
|
|
133441
134099
|
__name(observeCodeBuddyMarketplaceAfterResidual, "observeCodeBuddyMarketplaceAfterResidual");
|
|
133442
134100
|
__name(exactPluginObservation, "exactPluginObservation");
|
|
133443
134101
|
__name(normalizeGitSource, "normalizeGitSource");
|
|
134102
|
+
__name(normalizeHubGitSource, "normalizeHubGitSource");
|
|
134103
|
+
__name(observeQoderMarketplace, "observeQoderMarketplace");
|
|
134104
|
+
__name(observeQoderPlugin, "observeQoderPlugin");
|
|
134105
|
+
__name(readQoderManifest, "readQoderManifest");
|
|
134106
|
+
__name(assertQoderPluginManifest, "assertQoderPluginManifest");
|
|
134107
|
+
__name(assertFrozenQoderManifest, "assertFrozenQoderManifest");
|
|
134108
|
+
__name(assertQoderHubEntry, "assertQoderHubEntry");
|
|
134109
|
+
__name(verifyQoderInstalledPayload, "verifyQoderInstalledPayload");
|
|
134110
|
+
__name(runQoderUpdate, "runQoderUpdate");
|
|
133444
134111
|
__name(parseFrozenRemoteRef, "parseFrozenRemoteRef");
|
|
133445
134112
|
__name(preflightStructuredMarketplace, "preflightStructuredMarketplace");
|
|
133446
134113
|
__name(observeMarketplace, "observeMarketplace");
|
|
@@ -146705,10 +147372,10 @@ guardOutputStream(process.stdout);
|
|
|
146705
147372
|
guardOutputStream(process.stderr);
|
|
146706
147373
|
registerPathRedactor(redactSensitivePaths);
|
|
146707
147374
|
var execFile19 = promisify26(execFileCb22);
|
|
146708
|
-
var COMMANDS = /* @__PURE__ */ new Set(["help", "setup", "assess", "prepare", "approve", "publish", "reconcile", "verify", "postverify", "ship", "post-release", "attest", "hooks", "artifacts", "docs", "distribute", "route", "lineage"]);
|
|
147375
|
+
var COMMANDS = /* @__PURE__ */ new Set(["help", "setup", "assess", "prepare", "approve", "publish", "reconcile", "verify", "verify-records", "postverify", "ship", "post-release", "attest", "hooks", "artifacts", "docs", "distribute", "route", "lineage"]);
|
|
146709
147376
|
function printHubManualTargets(targets = []) {
|
|
146710
147377
|
for (const target of targets) {
|
|
146711
|
-
if (target.targetKind === "hub-backed") {
|
|
147378
|
+
if (target.targetKind === "hub-backed" && target.executionMode === "manual") {
|
|
146712
147379
|
console.log(`Manual host update: ${target.plugin} via Hub ${target.hub.name} (${target.host}). ${target.message}`);
|
|
146713
147380
|
}
|
|
146714
147381
|
}
|
|
@@ -146864,6 +147531,12 @@ async function performEnvironmentChecks() {
|
|
|
146864
147531
|
required: false,
|
|
146865
147532
|
usage: "\u4EC5\u4F9B\u53D1\u5E03\u540E\u7684\u4EBA\u5DE5\u5B89\u88C5\u4F7F\u7528\uFF1Brelease-skill \u4E0D\u6838\u9A8C CodeBuddy/WorkBuddy \u5B89\u88C5"
|
|
146866
147533
|
};
|
|
147534
|
+
const qoderCheck = await checkDependency("qoder", ["--version"]);
|
|
147535
|
+
checks.qoder = {
|
|
147536
|
+
...qoderCheck,
|
|
147537
|
+
required: false,
|
|
147538
|
+
usage: "\u4EC5\u4F9B\u53D1\u5E03\u540E\u7684\u53EF\u9009 Qoder \u672C\u673A\u66F4\u65B0\u4F7F\u7528\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6"
|
|
147539
|
+
};
|
|
146867
147540
|
return checks;
|
|
146868
147541
|
}
|
|
146869
147542
|
__name(performEnvironmentChecks, "performEnvironmentChecks");
|
|
@@ -146892,7 +147565,7 @@ function getCapabilityMaturity() {
|
|
|
146892
147565
|
publish: {
|
|
146893
147566
|
available: true,
|
|
146894
147567
|
mode: "controlled production (protocol-tested; no OS/network sandbox)",
|
|
146895
|
-
description: "Publishes frozen GitHub/npm artifacts after one readable-plan approval, runs automated Claude/Codex checkpoints, and emits non-blocking Kimi/CodeBuddy manual follow-ups"
|
|
147568
|
+
description: "Publishes frozen GitHub/npm artifacts after one readable-plan approval, runs automated Claude/Codex checkpoints, and emits non-blocking Kimi/CodeBuddy manual follow-ups plus the optional Qoder post-release update"
|
|
146896
147569
|
},
|
|
146897
147570
|
reconcile: {
|
|
146898
147571
|
available: true,
|
|
@@ -146902,7 +147575,12 @@ function getCapabilityMaturity() {
|
|
|
146902
147575
|
verify: {
|
|
146903
147576
|
available: true,
|
|
146904
147577
|
mode: "fresh consumer verification (protocol-tested; no OS/network sandbox)",
|
|
146905
|
-
description: "Recheck remote state, exact npm installation, CLI help, and automated Claude/Codex installs before VERIFIED; Kimi/CodeBuddy remain unverified manual follow-ups"
|
|
147578
|
+
description: "Recheck remote state, exact npm installation, CLI help, and automated Claude/Codex installs before VERIFIED; Kimi/CodeBuddy remain unverified manual follow-ups and Qoder remains optional post-release local work"
|
|
147579
|
+
},
|
|
147580
|
+
verifyRecords: {
|
|
147581
|
+
available: true,
|
|
147582
|
+
mode: "offline explicit-input verification",
|
|
147583
|
+
description: "Verify caller-supplied plan, approval, and release-run bytes without record discovery, remote access, process execution, or state changes"
|
|
146906
147584
|
},
|
|
146907
147585
|
postverify: {
|
|
146908
147586
|
available: true,
|
|
@@ -146942,6 +147620,7 @@ Commands:
|
|
|
146942
147620
|
publish Publish frozen GitHub/npm artifacts after approval
|
|
146943
147621
|
reconcile Resume PARTIAL state from evidence; conflicts require a human
|
|
146944
147622
|
verify Fresh remote and consumer verification; only this reaches VERIFIED
|
|
147623
|
+
verify-records Verify explicitly supplied historical plan, approval, and run records offline
|
|
146945
147624
|
postverify Run approved postVerify hooks from a VERIFIED run in an independent run
|
|
146946
147625
|
ship Resume one durable prepare -> approve -> publish -> verify flow; completes a parked
|
|
146947
147626
|
postVerify hook once its checkpoint approval is provided (--hook-approval)
|
|
@@ -146960,6 +147639,8 @@ Options:
|
|
|
146960
147639
|
--plan <path> Path to the release plan file (required for approve/publish/reconcile/verify/postverify)
|
|
146961
147640
|
--run <path> Path to the release run file, or a run directory whose release-run.json
|
|
146962
147641
|
is resolved automatically (required for reconcile/verify/postverify)
|
|
147642
|
+
--target-run <path> Target release-run.json for verify-records
|
|
147643
|
+
--source-run <path> Explicit predecessor release-run.json for verify-records (repeatable)
|
|
146963
147644
|
--approval <path> Path to the release-level approval record (required for publish/postverify)
|
|
146964
147645
|
--production Prepare immutable Git/npm production artifacts
|
|
146965
147646
|
--output <path> Override prepare/approve output path (non-production only)
|
|
@@ -146988,6 +147669,7 @@ Options:
|
|
|
146988
147669
|
--select-hooks <ids> Comma-separated proposal ids to adopt (propose-hooks mode)
|
|
146989
147670
|
--foundation-profile <path> Explicit foundation postPublish profile JSON (proposal input only; never auto-applied)
|
|
146990
147671
|
--unit <id> Select a release unit for prepare/ship (repeatable); docs refresh accepts one unit
|
|
147672
|
+
--target-version <version> Expected release unit version for prepare/ship/verify-records
|
|
146991
147673
|
--confirm-refresh <sha256:...> Confirm the exact dry-run refreshDigest before any document write
|
|
146992
147674
|
--ack-local-document-write Acknowledge the explicit local release-document write (docs refresh --write)
|
|
146993
147675
|
--platform <id> Legacy attestation platform: kimi or codebuddy
|
|
@@ -147000,7 +147682,7 @@ Options:
|
|
|
147000
147682
|
--hook-approval <path> Checkpoint approval for one requiresApproval hook (ship/distribute/postverify; repeatable)
|
|
147001
147683
|
--state <path> Override the durable ship state file
|
|
147002
147684
|
--update-local-hosts Update installed plugins for selected local hosts after VERIFIED
|
|
147003
|
-
--hosts <ids> Comma-separated local hosts for post-release update. No local host is updated unless --hosts contains at least one id
|
|
147685
|
+
--hosts <ids> Comma-separated local hosts for post-release update: claude,codex,kimi,codebuddy,workbuddy,qoder. No local host is updated unless --hosts contains at least one id
|
|
147004
147686
|
--confirm-plan <digest> Confirm the exact VERIFIED plan before local host mutation
|
|
147005
147687
|
--no-hook-cache Force every prepare hook to run in full; neither read nor write the hook cache
|
|
147006
147688
|
--json Output results as JSON
|
|
@@ -147013,6 +147695,7 @@ Safety:
|
|
|
147013
147695
|
The ship command runs configured hooks and gates automatically. Frozen plan approval is the only
|
|
147014
147696
|
normal release-level approval; requiresApproval postPublish hooks keep independent checkpoint approvals.
|
|
147015
147697
|
Kimi/CodeBuddy installations are non-blocking manual follow-up tasks (not verified by system).
|
|
147698
|
+
Qoder is an optional post-release local update; it never becomes a release distribution checkpoint.
|
|
147016
147699
|
prepare copies current public files into a local snapshot; it does not rewrite source files.
|
|
147017
147700
|
- Default mode is offline (release-skill pipeline does no remote writes)
|
|
147018
147701
|
- prepare output goes to .release-skill/ directory only
|
|
@@ -147021,6 +147704,7 @@ Safety:
|
|
|
147021
147704
|
- docs refresh --write rewrites only declared README managed regions and the current CHANGELOG entry after exact refreshDigest confirmation; it never commits, pushes, tags, publishes, or installs.
|
|
147022
147705
|
- publish requires explicit approval; plan digest is auto-read from the plan file
|
|
147023
147706
|
- post-release local host updates are optional local mutations and never change VERIFIED
|
|
147707
|
+
- verify-records reads only the paths named on its command line; it never discovers records, contacts remotes, or changes release state
|
|
147024
147708
|
- publish consumes frozen Git/npm artifacts, never the live workspace
|
|
147025
147709
|
- existing remote objects and uncertain checks stop for human intervention
|
|
147026
147710
|
- production-equivalent protocol sandbox is verified; a real remote canary is not
|
|
@@ -147092,7 +147776,8 @@ if (!command || command === "help") {
|
|
|
147092
147776
|
claude: "\u58F0\u660E claude-plugin distribution \u65F6\u5FC5\u987B\u53EF\u7528",
|
|
147093
147777
|
codex: "\u58F0\u660E codex-plugin distribution \u65F6\u5FC5\u987B\u53EF\u7528",
|
|
147094
147778
|
kimi: "\u53D1\u5E03\u540E\u4EBA\u5DE5\u5B89\u88C5\u5F85\u529E\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u7CFB\u7EDF\u4E0D\u6838\u9A8C",
|
|
147095
|
-
codebuddy: "\u53D1\u5E03\u540E\u4EBA\u5DE5\u5B89\u88C5\u5F85\u529E\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u7CFB\u7EDF\u4E0D\u6838\u9A8C"
|
|
147779
|
+
codebuddy: "\u53D1\u5E03\u540E\u4EBA\u5DE5\u5B89\u88C5\u5F85\u529E\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u7CFB\u7EDF\u4E0D\u6838\u9A8C",
|
|
147780
|
+
qoder: "\u53D1\u5E03\u540E\u53EF\u9009\u672C\u673A\u66F4\u65B0\uFF1B\u4E0D\u5F71\u54CD\u751F\u4EA7\u53D1\u5E03\u5C31\u7EEA\u5EA6\uFF0C\u4E0D\u662F release distribution checkpoint"
|
|
147096
147781
|
}
|
|
147097
147782
|
}
|
|
147098
147783
|
},
|
|
@@ -147106,7 +147791,7 @@ if (!command || command === "help") {
|
|
|
147106
147791
|
onlinePrepare: "previous-public-baseline observation available; production mode freezes publish artifacts and fails closed on drift or unknown state",
|
|
147107
147792
|
publish: "GitHub/npm plus automated Claude/Codex consumer checkpoints are protocol-tested without an OS/network sandbox; one approval is required and the internal plan digest is checked automatically",
|
|
147108
147793
|
reconcile: "PARTIAL recovery is protocol-tested without an OS/network sandbox; remote conflicts require human intervention",
|
|
147109
|
-
verify: "fresh exact npm and Claude/Codex consumer installation checks are protocol-tested without an OS/network sandbox; Kimi/CodeBuddy are unverified manual follow-ups; command invocation authorizes configured gates"
|
|
147794
|
+
verify: "fresh exact npm and Claude/Codex consumer installation checks are protocol-tested without an OS/network sandbox; Kimi/CodeBuddy are unverified manual follow-ups and Qoder is optional post-release local work; command invocation authorizes configured gates"
|
|
147110
147795
|
},
|
|
147111
147796
|
recommendations: []
|
|
147112
147797
|
};
|
|
@@ -147238,6 +147923,92 @@ For parallel or cross-session versions, use .release-skill/ships/<version>.json.
|
|
|
147238
147923
|
}
|
|
147239
147924
|
await exitAfterFlush(0);
|
|
147240
147925
|
}
|
|
147926
|
+
if (command === "verify-records" && (args.includes("--help") || args.includes("-h"))) {
|
|
147927
|
+
console.log(`release-skill verify-records - Verify explicit historical release records offline
|
|
147928
|
+
|
|
147929
|
+
Usage:
|
|
147930
|
+
release-skill verify-records --plan <path> --approval <path> --target-run <path> --source-run <path>... --unit <id> --target-version <version> --json
|
|
147931
|
+
|
|
147932
|
+
Options:
|
|
147933
|
+
--plan <path> Explicit release-plan JSON file
|
|
147934
|
+
--approval <path> Explicit approval-record JSON file
|
|
147935
|
+
--target-run <path> Explicit target release-run JSON file
|
|
147936
|
+
--source-run <path> Explicit predecessor release-run JSON file (repeatable)
|
|
147937
|
+
--unit <id> Expected release unit id
|
|
147938
|
+
--target-version <version> Expected release unit version
|
|
147939
|
+
--json Required; output one JSON result
|
|
147940
|
+
-h, --help Show this help message and exit
|
|
147941
|
+
|
|
147942
|
+
The command reads only these explicit paths. It never scans .release-skill,
|
|
147943
|
+
follows paths carried inside records, contacts a remote, writes a file, or
|
|
147944
|
+
changes release state.`);
|
|
147945
|
+
await exitAfterFlush(0);
|
|
147946
|
+
}
|
|
147947
|
+
if (command === "verify-records") {
|
|
147948
|
+
const value = /* @__PURE__ */ __name((flag) => {
|
|
147949
|
+
const index = args.indexOf(flag);
|
|
147950
|
+
return index !== -1 && args[index + 1] && !args[index + 1].startsWith("--") ? args[index + 1] : void 0;
|
|
147951
|
+
}, "value");
|
|
147952
|
+
const values = /* @__PURE__ */ __name((flag) => {
|
|
147953
|
+
const selected = [];
|
|
147954
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
147955
|
+
if (args[index] === flag && args[index + 1] && !args[index + 1].startsWith("--")) {
|
|
147956
|
+
selected.push(args[index + 1]);
|
|
147957
|
+
}
|
|
147958
|
+
}
|
|
147959
|
+
return selected;
|
|
147960
|
+
}, "values");
|
|
147961
|
+
const planPath = value("--plan");
|
|
147962
|
+
const approvalPath = value("--approval");
|
|
147963
|
+
const targetRunPath = value("--target-run");
|
|
147964
|
+
const sourceRunPaths = values("--source-run");
|
|
147965
|
+
const unitId = value("--unit");
|
|
147966
|
+
const targetVersion = value("--target-version");
|
|
147967
|
+
if (!hasJson) {
|
|
147968
|
+
console.log(JSON.stringify({
|
|
147969
|
+
status: "INSUFFICIENT",
|
|
147970
|
+
unitId: unitId ?? null,
|
|
147971
|
+
targetVersion: targetVersion ?? null,
|
|
147972
|
+
historicalTerminalStatus: null,
|
|
147973
|
+
inputs: {},
|
|
147974
|
+
digests: {},
|
|
147975
|
+
findings: [{
|
|
147976
|
+
code: "INPUT_MISSING",
|
|
147977
|
+
role: "cli",
|
|
147978
|
+
message: "--json is required for verify-records"
|
|
147979
|
+
}]
|
|
147980
|
+
}));
|
|
147981
|
+
await exitAfterFlush(2);
|
|
147982
|
+
}
|
|
147983
|
+
const readInput = /* @__PURE__ */ __name(async (path40) => {
|
|
147984
|
+
if (!path40) return void 0;
|
|
147985
|
+
try {
|
|
147986
|
+
return { bytes: await readFile57(path40), source: path40 };
|
|
147987
|
+
} catch {
|
|
147988
|
+
return { source: path40 };
|
|
147989
|
+
}
|
|
147990
|
+
}, "readInput");
|
|
147991
|
+
const [plan, approval, targetRun, ...sourceRuns] = await Promise.all([
|
|
147992
|
+
readInput(planPath),
|
|
147993
|
+
readInput(approvalPath),
|
|
147994
|
+
readInput(targetRunPath),
|
|
147995
|
+
...sourceRunPaths.map((path40) => readInput(path40))
|
|
147996
|
+
]);
|
|
147997
|
+
const {
|
|
147998
|
+
VERIFY_RECORDS_EXIT_CODES: VERIFY_RECORDS_EXIT_CODES2,
|
|
147999
|
+
verifyReleaseRecords: verifyReleaseRecords2
|
|
148000
|
+
} = await init_verify_records().then(() => verify_records_exports);
|
|
148001
|
+
const result2 = verifyReleaseRecords2({
|
|
148002
|
+
plan,
|
|
148003
|
+
approval,
|
|
148004
|
+
targetRun,
|
|
148005
|
+
sourceRuns,
|
|
148006
|
+
unitId,
|
|
148007
|
+
targetVersion
|
|
148008
|
+
});
|
|
148009
|
+
console.log(JSON.stringify(result2));
|
|
148010
|
+
await exitAfterFlush(VERIFY_RECORDS_EXIT_CODES2[result2.status]);
|
|
148011
|
+
}
|
|
147241
148012
|
if (command === "setup") {
|
|
147242
148013
|
const rootIdx = args.indexOf("--root");
|
|
147243
148014
|
const rawRoot = rootIdx !== -1 && args[rootIdx + 1] ? args[rootIdx + 1] : process.cwd();
|
|
@@ -148166,6 +148937,23 @@ This command creates an independent postVerify run and never reads or writes shi
|
|
|
148166
148937
|
root,
|
|
148167
148938
|
...postpublishApprovalPaths.length > 0 ? { postpublishApprovalPaths } : {}
|
|
148168
148939
|
});
|
|
148940
|
+
if (result2.status === "DISTRIBUTED") {
|
|
148941
|
+
const {
|
|
148942
|
+
derivePostReleaseChecklist: derivePostReleaseChecklist2,
|
|
148943
|
+
unavailablePostReleaseChecklist: unavailablePostReleaseChecklist2
|
|
148944
|
+
} = await init_post_release_local().then(() => post_release_local_exports);
|
|
148945
|
+
let plan;
|
|
148946
|
+
try {
|
|
148947
|
+
plan = JSON.parse(await readFile57(resolve42(planPath), "utf8"));
|
|
148948
|
+
result2.postRelease = derivePostReleaseChecklist2(plan, {
|
|
148949
|
+
root,
|
|
148950
|
+
runPath: result2.runPath,
|
|
148951
|
+
postVerifyComplete: true
|
|
148952
|
+
});
|
|
148953
|
+
} catch (error) {
|
|
148954
|
+
result2.postRelease = unavailablePostReleaseChecklist2(plan, error);
|
|
148955
|
+
}
|
|
148956
|
+
}
|
|
148169
148957
|
if (hasJson) {
|
|
148170
148958
|
console.log(JSON.stringify(result2, null, 2));
|
|
148171
148959
|
} else {
|
|
@@ -148174,6 +148962,17 @@ This command creates an independent postVerify run and never reads or writes shi
|
|
|
148174
148962
|
console.log(` ${cp4.actionId}: ${cp4.status}`);
|
|
148175
148963
|
}
|
|
148176
148964
|
if (result2.runPath) console.log(`PostVerify run: ${result2.runPath}`);
|
|
148965
|
+
if (result2.postRelease?.status === "UNAVAILABLE") {
|
|
148966
|
+
console.warn(`Post-release checklist unavailable: ${result2.postRelease.diagnostic?.message ?? "unknown error"}`);
|
|
148967
|
+
} else if (result2.postRelease?.localHostUpdate?.promptRequired === true) {
|
|
148968
|
+
const localHostUpdate = result2.postRelease.localHostUpdate;
|
|
148969
|
+
console.log(`Post-release: ask whether to update local host plugins (${localHostUpdate.hosts.join(", ")}).`);
|
|
148970
|
+
if (localHostUpdate.available === true && typeof localHostUpdate.runPath === "string") {
|
|
148971
|
+
console.log(`Post-release command: release-skill post-release --plan ${resolve42(planPath)} --run ${localHostUpdate.runPath}`);
|
|
148972
|
+
console.log("Choose --hosts before adding --update-local-hosts to perform a local update.");
|
|
148973
|
+
}
|
|
148974
|
+
printHubManualTargets(localHostUpdate.targets);
|
|
148975
|
+
}
|
|
148177
148976
|
}
|
|
148178
148977
|
await exitAfterFlush(result2.status === "DISTRIBUTED" ? 0 : 1);
|
|
148179
148978
|
} catch (err) {
|