@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const DEMO_ASSET_NAMES = ['gif', 'transcript', 'sha256'];
|
|
5
|
+
const JOURNAL_RELATIVE_PATH = 'assets/.demo-init.promotion.json';
|
|
6
|
+
const PROMOTION_DIR_PREFIX = '.demo-init.promotion-';
|
|
7
|
+
function sha256Bytes(value) {
|
|
8
|
+
return crypto.createHash('sha256').update(value).digest('hex');
|
|
9
|
+
}
|
|
10
|
+
function sha256File(filePath, io = fs) {
|
|
11
|
+
return sha256Bytes(io.readFileSync(filePath));
|
|
12
|
+
}
|
|
13
|
+
function assetTargets(cwd) {
|
|
14
|
+
return {
|
|
15
|
+
gif: path.join(cwd, 'assets', 'demo-init.gif'),
|
|
16
|
+
transcript: path.join(cwd, 'assets', 'demo-init.transcript'),
|
|
17
|
+
sha256: path.join(cwd, 'assets', 'demo-init.transcript.sha256')
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function journalPath(cwd) {
|
|
21
|
+
return path.join(cwd, JOURNAL_RELATIVE_PATH);
|
|
22
|
+
}
|
|
23
|
+
function isUnsupportedFsyncError(error) {
|
|
24
|
+
const code = error.code;
|
|
25
|
+
return code === 'EINVAL' || code === 'ENOSYS' || code === 'ENOTSUP' || code === 'EPERM';
|
|
26
|
+
}
|
|
27
|
+
function syncFile(filePath, io) {
|
|
28
|
+
if (!io.openSync || !io.closeSync || !io.fsyncSync)
|
|
29
|
+
return;
|
|
30
|
+
const descriptor = io.openSync(filePath, 'r');
|
|
31
|
+
try {
|
|
32
|
+
try {
|
|
33
|
+
io.fsyncSync(descriptor);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!isUnsupportedFsyncError(error))
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
io.closeSync(descriptor);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function writeJournal(cwd, journal, io) {
|
|
45
|
+
const target = journalPath(cwd);
|
|
46
|
+
const temporary = `${target}.tmp-${process.pid}`;
|
|
47
|
+
io.writeFileSync(temporary, `${JSON.stringify(journal, null, 2)}\n`);
|
|
48
|
+
syncFile(temporary, io);
|
|
49
|
+
try {
|
|
50
|
+
io.renameSync(temporary, target);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
try {
|
|
54
|
+
io.unlinkSync(temporary);
|
|
55
|
+
}
|
|
56
|
+
catch { /* preserve primary error */ }
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function existingRegularFile(filePath, io) {
|
|
61
|
+
if (!io.existsSync(filePath))
|
|
62
|
+
return false;
|
|
63
|
+
const stat = io.lstatSync(filePath);
|
|
64
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
65
|
+
throw new Error(`promotion target is not a regular file: ${filePath}`);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function removeIfPresent(filePath, io) {
|
|
69
|
+
if (io.existsSync(filePath))
|
|
70
|
+
io.unlinkSync(filePath);
|
|
71
|
+
}
|
|
72
|
+
function samePath(left, right) {
|
|
73
|
+
return path.normalize(path.resolve(left)) === path.normalize(path.resolve(right));
|
|
74
|
+
}
|
|
75
|
+
function pathIsInside(root, candidate) {
|
|
76
|
+
const relative = path.relative(root, candidate);
|
|
77
|
+
return relative !== '' && !path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`);
|
|
78
|
+
}
|
|
79
|
+
function existingStat(filePath, io) {
|
|
80
|
+
try {
|
|
81
|
+
return io.lstatSync(filePath);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error.code === 'ENOENT')
|
|
85
|
+
return null;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function validatePromotionJournal(cwd, journal, io) {
|
|
90
|
+
if (!journal || typeof journal !== 'object')
|
|
91
|
+
throw new Error('promotion journal is invalid');
|
|
92
|
+
if (journal.version !== 1 || !['prepared', 'promoting', 'committed'].includes(journal.phase)) {
|
|
93
|
+
throw new Error('promotion journal is invalid');
|
|
94
|
+
}
|
|
95
|
+
if (typeof journal.generation !== 'string' || !journal.generation || /[\r\n]/u.test(journal.generation)) {
|
|
96
|
+
throw new Error('promotion journal generation is invalid');
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(journal.entries) || journal.entries.length !== DEMO_ASSET_NAMES.length) {
|
|
99
|
+
throw new Error('promotion journal entries are invalid');
|
|
100
|
+
}
|
|
101
|
+
if (!Number.isInteger(journal.currentIndex) || journal.currentIndex < -1 || journal.currentIndex >= journal.entries.length) {
|
|
102
|
+
throw new Error('promotion journal index is invalid');
|
|
103
|
+
}
|
|
104
|
+
const assets = path.join(cwd, 'assets');
|
|
105
|
+
const assetsStat = existingStat(assets, io);
|
|
106
|
+
if (!assetsStat || assetsStat.isSymbolicLink() || !assetsStat.isDirectory()) {
|
|
107
|
+
throw new Error('promotion assets directory is invalid');
|
|
108
|
+
}
|
|
109
|
+
if (typeof journal.directory !== 'string' || path.isAbsolute(journal.directory)) {
|
|
110
|
+
throw new Error('promotion journal directory is invalid');
|
|
111
|
+
}
|
|
112
|
+
const directory = path.resolve(cwd, journal.directory);
|
|
113
|
+
const directoryName = path.basename(directory);
|
|
114
|
+
if (!directoryName.startsWith(PROMOTION_DIR_PREFIX) || directoryName === PROMOTION_DIR_PREFIX
|
|
115
|
+
|| journal.directory !== path.relative(cwd, directory)
|
|
116
|
+
|| !samePath(directory, path.join(assets, directoryName))) {
|
|
117
|
+
throw new Error('promotion journal directory is invalid');
|
|
118
|
+
}
|
|
119
|
+
const directoryStat = existingStat(directory, io);
|
|
120
|
+
if (directoryStat && (directoryStat.isSymbolicLink() || !directoryStat.isDirectory())) {
|
|
121
|
+
throw new Error('promotion journal directory is invalid');
|
|
122
|
+
}
|
|
123
|
+
const targets = assetTargets(cwd);
|
|
124
|
+
for (const [index, name] of DEMO_ASSET_NAMES.entries()) {
|
|
125
|
+
const entry = journal.entries[index];
|
|
126
|
+
if (!entry || entry.name !== name || typeof entry.target !== 'string' || typeof entry.staging !== 'string'
|
|
127
|
+
|| typeof entry.backup !== 'string' || typeof entry.existed !== 'boolean'
|
|
128
|
+
|| (entry.oldSha256 !== null && typeof entry.oldSha256 !== 'string')
|
|
129
|
+
|| typeof entry.newSha256 !== 'string') {
|
|
130
|
+
throw new Error('promotion journal entry is invalid');
|
|
131
|
+
}
|
|
132
|
+
const target = targets[name];
|
|
133
|
+
const staging = path.join(directory, path.basename(target));
|
|
134
|
+
const backup = path.join(directory, `old-${name}`);
|
|
135
|
+
if (!samePath(entry.target, target) || !samePath(entry.staging, staging) || !samePath(entry.backup, backup)) {
|
|
136
|
+
throw new Error(`promotion journal path is invalid: ${name}`);
|
|
137
|
+
}
|
|
138
|
+
for (const [label, filePath] of [['target', target], ['staging', staging], ['backup', backup]]) {
|
|
139
|
+
if (!pathIsInside(assets, filePath))
|
|
140
|
+
throw new Error(`promotion journal path is invalid: ${name}.${label}`);
|
|
141
|
+
const stat = existingStat(filePath, io);
|
|
142
|
+
if (stat && (stat.isSymbolicLink() || !stat.isFile())) {
|
|
143
|
+
throw new Error(`promotion journal path is invalid: ${name}.${label}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (entry.existed !== (entry.oldSha256 !== null)) {
|
|
147
|
+
throw new Error(`promotion journal entry is invalid: ${name}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return journal;
|
|
151
|
+
}
|
|
152
|
+
function cleanupJournal(journal, cwd, io) {
|
|
153
|
+
for (const entry of journal.entries) {
|
|
154
|
+
removeIfPresent(entry.staging, io);
|
|
155
|
+
removeIfPresent(entry.backup, io);
|
|
156
|
+
}
|
|
157
|
+
io.rmSync(path.join(cwd, journal.directory), { recursive: true, force: true });
|
|
158
|
+
removeIfPresent(journalPath(cwd), io);
|
|
159
|
+
}
|
|
160
|
+
function verifyTargets(entries, io, expected) {
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
if (!entry.existed && expected === 'old') {
|
|
163
|
+
if (io.existsSync(entry.target))
|
|
164
|
+
throw new Error(`old promotion target unexpectedly exists: ${entry.target}`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (!io.existsSync(entry.target))
|
|
168
|
+
throw new Error(`promotion target is missing: ${entry.target}`);
|
|
169
|
+
const actual = sha256File(entry.target, io);
|
|
170
|
+
const wanted = expected === 'old' ? entry.oldSha256 : entry.newSha256;
|
|
171
|
+
if (!wanted || actual !== wanted)
|
|
172
|
+
throw new Error(`promotion target has unexpected content: ${entry.target}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function restoreOld(journal, io) {
|
|
176
|
+
for (const entry of [...journal.entries].reverse()) {
|
|
177
|
+
if (!entry.existed) {
|
|
178
|
+
removeIfPresent(entry.target, io);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!io.existsSync(entry.backup) || sha256File(entry.backup, io) !== entry.oldSha256) {
|
|
182
|
+
throw new Error(`promotion backup is missing or corrupt: ${entry.backup}`);
|
|
183
|
+
}
|
|
184
|
+
const restore = `${entry.target}.restore-${process.pid}`;
|
|
185
|
+
io.copyFileSync(entry.backup, restore);
|
|
186
|
+
removeIfPresent(entry.target, io);
|
|
187
|
+
io.renameSync(restore, entry.target);
|
|
188
|
+
}
|
|
189
|
+
verifyTargets(journal.entries, io, 'old');
|
|
190
|
+
}
|
|
191
|
+
function recoveryFailure(error) {
|
|
192
|
+
return {
|
|
193
|
+
status: 'failed',
|
|
194
|
+
code: 'DEMO_PROMOTION_RECOVERY_REQUIRED',
|
|
195
|
+
message: error instanceof Error ? error.message : String(error)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function recoverDemoPromotion(cwd, io = fs) {
|
|
199
|
+
const target = journalPath(cwd);
|
|
200
|
+
if (!io.existsSync(target))
|
|
201
|
+
return null;
|
|
202
|
+
let journal;
|
|
203
|
+
try {
|
|
204
|
+
const journalStat = io.lstatSync(target);
|
|
205
|
+
if (journalStat.isSymbolicLink() || !journalStat.isFile())
|
|
206
|
+
throw new Error('promotion journal is invalid');
|
|
207
|
+
journal = JSON.parse(io.readFileSync(target, 'utf8'));
|
|
208
|
+
journal = validatePromotionJournal(cwd, journal, io);
|
|
209
|
+
if (journal.phase === 'committed') {
|
|
210
|
+
try {
|
|
211
|
+
verifyTargets(journal.entries, io, 'new');
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
restoreOld(journal, io);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
restoreOld(journal, io);
|
|
219
|
+
}
|
|
220
|
+
cleanupJournal(journal, cwd, io);
|
|
221
|
+
return { status: 'committed', code: null, message: null };
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
return recoveryFailure(error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function createDemoPromotion(cwd, transcript) {
|
|
228
|
+
const assets = path.join(cwd, 'assets');
|
|
229
|
+
const directory = fs.mkdtempSync(path.join(assets, PROMOTION_DIR_PREFIX));
|
|
230
|
+
const staging = {
|
|
231
|
+
gif: path.join(directory, 'demo-init.gif'),
|
|
232
|
+
transcript: path.join(directory, 'demo-init.transcript'),
|
|
233
|
+
sha256: path.join(directory, 'demo-init.transcript.sha256')
|
|
234
|
+
};
|
|
235
|
+
const sha256 = sha256Bytes(Buffer.from(transcript, 'utf8'));
|
|
236
|
+
fs.writeFileSync(staging.transcript, transcript);
|
|
237
|
+
fs.writeFileSync(staging.sha256, `${sha256}\n`);
|
|
238
|
+
return { directory, staging, sha256 };
|
|
239
|
+
}
|
|
240
|
+
function promoteDemoAssets(cwd, staging, generation, io = fs) {
|
|
241
|
+
const targets = assetTargets(cwd);
|
|
242
|
+
const directory = path.relative(cwd, path.dirname(staging.gif));
|
|
243
|
+
const journal = {
|
|
244
|
+
version: 1,
|
|
245
|
+
generation,
|
|
246
|
+
phase: 'prepared',
|
|
247
|
+
currentIndex: -1,
|
|
248
|
+
directory,
|
|
249
|
+
entries: []
|
|
250
|
+
};
|
|
251
|
+
try {
|
|
252
|
+
for (const name of DEMO_ASSET_NAMES) {
|
|
253
|
+
if (!io.existsSync(staging[name]))
|
|
254
|
+
throw new Error(`staged demo asset is missing: ${staging[name]}`);
|
|
255
|
+
const target = targets[name];
|
|
256
|
+
const backup = path.join(path.dirname(staging[name]), `old-${name}`);
|
|
257
|
+
const existed = existingRegularFile(target, io);
|
|
258
|
+
const oldSha256 = existed ? sha256File(target, io) : null;
|
|
259
|
+
if (existed) {
|
|
260
|
+
io.copyFileSync(target, backup);
|
|
261
|
+
if (sha256File(backup, io) !== oldSha256)
|
|
262
|
+
throw new Error(`promotion backup verification failed: ${target}`);
|
|
263
|
+
}
|
|
264
|
+
journal.entries.push({
|
|
265
|
+
name, target, staging: staging[name], backup, existed, oldSha256,
|
|
266
|
+
newSha256: sha256File(staging[name], io)
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
writeJournal(cwd, journal, io);
|
|
270
|
+
for (let index = 0; index < journal.entries.length; index += 1) {
|
|
271
|
+
journal.phase = 'promoting';
|
|
272
|
+
journal.currentIndex = index;
|
|
273
|
+
writeJournal(cwd, journal, io);
|
|
274
|
+
const entry = journal.entries[index];
|
|
275
|
+
removeIfPresent(entry.target, io);
|
|
276
|
+
io.renameSync(entry.staging, entry.target);
|
|
277
|
+
if (sha256File(entry.target, io) !== entry.newSha256)
|
|
278
|
+
throw new Error(`promoted asset verification failed: ${entry.target}`);
|
|
279
|
+
writeJournal(cwd, journal, io);
|
|
280
|
+
}
|
|
281
|
+
verifyTargets(journal.entries, io, 'new');
|
|
282
|
+
journal.phase = 'committed';
|
|
283
|
+
writeJournal(cwd, journal, io);
|
|
284
|
+
try {
|
|
285
|
+
cleanupJournal(journal, cwd, io);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
return {
|
|
289
|
+
status: 'failed',
|
|
290
|
+
code: 'DEMO_PROMOTION_CLEANUP_FAILED',
|
|
291
|
+
message: error instanceof Error ? error.message : String(error)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return { status: 'committed', code: null, message: null };
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
try {
|
|
298
|
+
if (io.existsSync(journalPath(cwd))) {
|
|
299
|
+
const persisted = validatePromotionJournal(cwd, JSON.parse(io.readFileSync(journalPath(cwd), 'utf8')), io);
|
|
300
|
+
if (persisted.phase === 'committed') {
|
|
301
|
+
verifyTargets(persisted.entries, io, 'new');
|
|
302
|
+
return {
|
|
303
|
+
status: 'failed',
|
|
304
|
+
code: 'DEMO_PROMOTION_CLEANUP_FAILED',
|
|
305
|
+
message: error instanceof Error ? error.message : String(error)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
restoreOld(persisted, io);
|
|
309
|
+
cleanupJournal(persisted, cwd, io);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
io.rmSync(path.dirname(staging.gif), { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
return { status: 'failed', code: 'DEMO_PROMOTION_FAILED', message: error instanceof Error ? error.message : String(error) };
|
|
315
|
+
}
|
|
316
|
+
catch (rollbackError) {
|
|
317
|
+
return recoveryFailure(rollbackError);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
export { DEMO_ASSET_NAMES, JOURNAL_RELATIVE_PATH, assetTargets, createDemoPromotion, journalPath, promoteDemoAssets, recoverDemoPromotion, sha256Bytes };
|
|
322
|
+
//# sourceMappingURL=demo-promotion.js.map
|