@fitlab-ai/agent-infra 0.9.6 → 0.9.8
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/dist/bin/internal-cli.js +10 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
- package/dist/lib/decide.js +68 -14
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +1 -3
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +48 -8
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/pr-summary.js +5 -3
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +394 -264
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +23 -9
- package/dist/lib/sandbox/control/lifecycle.js +590 -61
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +51 -25
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/mounts.js +7 -0
- package/dist/lib/sandbox/recovery.js +135 -50
- package/dist/lib/sandbox/workspace-view.js +59 -20
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +107 -20
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +120 -17
- package/dist/lib/task/events.js +148 -7
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/orchestration.js +293 -18
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +80 -22
- package/dist/lib/task/verification.js +13 -1
- package/dist/lib/task/workflow-warning-intents.js +4 -2
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
- package/lib/decide.ts +59 -14
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +1 -3
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +43 -8
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/pr-summary.ts +10 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +139 -6
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +24 -12
- package/lib/sandbox/control/lifecycle.ts +685 -67
- package/lib/sandbox/control/protocol.ts +35 -6
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +67 -33
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/mounts.ts +8 -0
- package/lib/sandbox/recovery.ts +159 -49
- package/lib/sandbox/workspace-view.ts +73 -22
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +129 -27
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +205 -25
- package/lib/task/events.ts +132 -6
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/ledger.ts +16 -1
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/orchestration.ts +443 -22
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -32
- package/lib/task/verification.ts +15 -1
- package/lib/task/workflow-warning-intents.ts +4 -2
- package/lib/task/write.ts +4 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -151,7 +151,7 @@ function checkOrchestrationState({ taskDir }) {
|
|
|
151
151
|
if (isFailClosedLegacyPause(run)) {
|
|
152
152
|
return passResult('orchestration-state', 'Legacy orchestration run is safely paused');
|
|
153
153
|
}
|
|
154
|
-
if (run.schemaVersion
|
|
154
|
+
if (![2, 3].includes(run.schemaVersion) || !['paused', 'completed'].includes(run.status)) {
|
|
155
155
|
return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
|
|
156
156
|
}
|
|
157
157
|
if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
|
|
@@ -261,7 +261,22 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
261
261
|
&& guards?.commitAuthorizationUnused === true
|
|
262
262
|
&& guards?.completionEvidenceAbsent === true
|
|
263
263
|
&& guards?.commitIntentAbsent === true;
|
|
264
|
-
|
|
264
|
+
const validClaudeCodeRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED_NO_MIGRATION'
|
|
265
|
+
&& recovery.previousSchemaVersion === 3
|
|
266
|
+
&& recovery.previousStatus === 'paused'
|
|
267
|
+
&& recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
268
|
+
&& recovery.client === 'claude-code'
|
|
269
|
+
&& recovery.resultingStatus === 'running'
|
|
270
|
+
&& exactText(recovery.recoveredAt)
|
|
271
|
+
&& guards?.stepCount === 0
|
|
272
|
+
&& guards?.nextStage === null
|
|
273
|
+
&& guards?.baselineEmpty === true
|
|
274
|
+
&& guards?.receiptCount === 0
|
|
275
|
+
&& guards?.pendingDelegation === false
|
|
276
|
+
&& guards?.commitAuthorizationUnused === true
|
|
277
|
+
&& guards?.completionEvidenceAbsent === true
|
|
278
|
+
&& guards?.commitIntentAbsent === true;
|
|
279
|
+
if (!validModelRecovery && !validCapabilityRecovery && !validClaudeCodeRecovery) {
|
|
265
280
|
return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
|
|
266
281
|
}
|
|
267
282
|
}
|
|
@@ -292,26 +307,37 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
292
307
|
}
|
|
293
308
|
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
|
|
294
309
|
if (activated) {
|
|
295
|
-
|
|
296
|
-
|
|
310
|
+
const isClaudeCode = receipt.client === 'claude-code';
|
|
311
|
+
if (!isClaudeCode) {
|
|
312
|
+
if (!exactText(receipt.actualModel)) {
|
|
313
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual model`);
|
|
314
|
+
}
|
|
315
|
+
if (!exactText(receipt.actualReasoningEffort)) {
|
|
316
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
|
|
317
|
+
}
|
|
297
318
|
}
|
|
298
|
-
|
|
299
|
-
|
|
319
|
+
// parentId/childId 校验对所有 client 保持统一(不新增分支);spawnMode 检查按 client 判断
|
|
320
|
+
if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId) {
|
|
321
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid delegation identity`);
|
|
300
322
|
}
|
|
301
|
-
if (!
|
|
323
|
+
if (!isClaudeCode && receipt.spawnMode !== 'fresh') {
|
|
302
324
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
|
|
303
325
|
}
|
|
304
|
-
if (receipt.actualModel
|
|
305
|
-
|
|
326
|
+
if (receipt.actualModel != null && receipt.actualModel !== receipt.requestedModel) {
|
|
327
|
+
if (!isClaudeCode && !exactText(receipt.modelFallbackReason)) {
|
|
328
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' model fallback is not justified`);
|
|
329
|
+
}
|
|
306
330
|
}
|
|
307
|
-
if (receipt.actualModel
|
|
331
|
+
if ((receipt.actualModel == null || receipt.actualModel === receipt.requestedModel)
|
|
332
|
+
&& receipt.modelFallbackReason !== null) {
|
|
308
333
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated model fallback reason`);
|
|
309
334
|
}
|
|
310
|
-
if (receipt.actualReasoningEffort !== receipt.requestedReasoningEffort
|
|
311
|
-
&& !exactText(receipt.reasoningEffortFallbackReason)) {
|
|
312
|
-
|
|
335
|
+
if (receipt.actualReasoningEffort != null && receipt.actualReasoningEffort !== receipt.requestedReasoningEffort) {
|
|
336
|
+
if (!isClaudeCode && !exactText(receipt.reasoningEffortFallbackReason)) {
|
|
337
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' reasoning-effort fallback is not justified`);
|
|
338
|
+
}
|
|
313
339
|
}
|
|
314
|
-
if (receipt.actualReasoningEffort === receipt.requestedReasoningEffort
|
|
340
|
+
if ((receipt.actualReasoningEffort == null || receipt.actualReasoningEffort === receipt.requestedReasoningEffort)
|
|
315
341
|
&& receipt.reasoningEffortFallbackReason !== null) {
|
|
316
342
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
|
|
317
343
|
}
|
|
@@ -774,7 +800,7 @@ function checkReviewLedger({ taskDir, config }) {
|
|
|
774
800
|
return passResult("review-ledger", "Disagreement ledger has no entries");
|
|
775
801
|
}
|
|
776
802
|
const stageScope = Array.isArray(config.stage_scope) ? config.stage_scope : null;
|
|
777
|
-
const maxRounds = Number(resolveReviewSetting(config, "
|
|
803
|
+
const maxRounds = Number(resolveReviewSetting(config, "maxHandshakeRounds", DEFAULT_MAX_HANDSHAKE_ROUNDS));
|
|
778
804
|
const problems = [];
|
|
779
805
|
let inScopeCount = 0;
|
|
780
806
|
for (const cells of rows) {
|
|
@@ -916,7 +942,13 @@ function checkPostReviewCommit({ taskDir, config }) {
|
|
|
916
942
|
return blockedResult("post-review-commit", relation.message, relation.code);
|
|
917
943
|
}
|
|
918
944
|
if (relation.status === "strict") {
|
|
919
|
-
return
|
|
945
|
+
return resolvePostReviewIdentityFailure(task.content, {
|
|
946
|
+
code: "PR_MERGE_IDENTITY_INVALID",
|
|
947
|
+
message: "Merged PR evidence did not establish squash merge equivalence"
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
if (relation.code === "PR_MERGE_IDENTITY_INVALID") {
|
|
951
|
+
return resolvePostReviewIdentityFailure(task.content, relation);
|
|
920
952
|
}
|
|
921
953
|
return failResult("post-review-commit", relation.message, relation.code);
|
|
922
954
|
}
|
|
@@ -981,21 +1013,47 @@ function checkPostReviewCommit({ taskDir, config }) {
|
|
|
981
1013
|
}
|
|
982
1014
|
const exemption = resolvePostReviewExemption(task.content);
|
|
983
1015
|
if (!exemption.ok)
|
|
984
|
-
return failResult("post-review-commit", exemption.message);
|
|
985
|
-
if (exemption.
|
|
986
|
-
return passResult("post-review-commit", `${commits.length} post-review commit(s) covered by a human-decided exemption`);
|
|
1016
|
+
return failResult("post-review-commit", exemption.message, exemption.code);
|
|
1017
|
+
if (exemption.exemptions.length > 0) {
|
|
1018
|
+
return passResult("post-review-commit", `${commits.length} post-review commit(s) covered by a human-decided exemption: ${formatPostReviewExemptions(exemption.exemptions)}`);
|
|
987
1019
|
}
|
|
988
1020
|
return failResult("post-review-commit", `${commits.length} commit(s) to code/rule paths after reviewed commit ${sha.slice(0, 8)}; re-run review-code or record a human-decided exemption`);
|
|
989
1021
|
}
|
|
1022
|
+
function resolvePostReviewIdentityFailure(content, failure) {
|
|
1023
|
+
const exemption = resolvePostReviewExemption(content);
|
|
1024
|
+
if (!exemption.ok) {
|
|
1025
|
+
return failResult("post-review-commit", exemption.message, exemption.code);
|
|
1026
|
+
}
|
|
1027
|
+
if (exemption.exemptions.length === 0) {
|
|
1028
|
+
return failResult("post-review-commit", failure.message, failure.code);
|
|
1029
|
+
}
|
|
1030
|
+
return passResult("post-review-commit", `Human-decided post-review exemption overrode ${failure.code}: ${failure.message}; ${formatPostReviewExemptions(exemption.exemptions)}`);
|
|
1031
|
+
}
|
|
1032
|
+
function formatPostReviewExemptions(exemptions) {
|
|
1033
|
+
return exemptions.map((row) => `${row.id}: ${row.evidence}`).join("; ");
|
|
1034
|
+
}
|
|
990
1035
|
function resolvePostReviewExemption(content) {
|
|
991
1036
|
try {
|
|
1037
|
+
const rows = parseLedger(content);
|
|
1038
|
+
const invalid = validateLedgerRows(rows);
|
|
1039
|
+
if (invalid) {
|
|
1040
|
+
return {
|
|
1041
|
+
ok: false,
|
|
1042
|
+
code: "POST_REVIEW_EXEMPTION_INVALID",
|
|
1043
|
+
message: `${invalid.code}: ${invalid.message}`
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
992
1046
|
return {
|
|
993
1047
|
ok: true,
|
|
994
|
-
|
|
1048
|
+
exemptions: rows.filter((row) => row.stage === POST_REVIEW_COMMIT_STAGE)
|
|
995
1049
|
};
|
|
996
1050
|
}
|
|
997
|
-
catch {
|
|
998
|
-
return {
|
|
1051
|
+
catch (error) {
|
|
1052
|
+
return {
|
|
1053
|
+
ok: false,
|
|
1054
|
+
code: "POST_REVIEW_EXEMPTION_INVALID",
|
|
1055
|
+
message: `Invalid disagreement ledger: ${error instanceof Error ? error.message : String(error)}`
|
|
1056
|
+
};
|
|
999
1057
|
}
|
|
1000
1058
|
}
|
|
1001
1059
|
function checkReviewFact({ taskDir, artifactFile }) {
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { verifyInProcess } from './verification-engine.js';
|
|
5
5
|
import { parseArtifactName } from './artifact-lifecycle.js';
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
|
+
import { allowsManualOverride } from './guard-override.js';
|
|
7
8
|
const gate = (skill, expectedState, artifactFamily) => ({
|
|
8
9
|
skill, expectedState, mode: 'gate', ...(artifactFamily ? { artifactFamily } : {})
|
|
9
10
|
});
|
|
@@ -47,7 +48,7 @@ function verifyTaskEvent(request, options = {}) {
|
|
|
47
48
|
if (!resolved.ok)
|
|
48
49
|
return failure(request, resolved.code, resolved.message, { taskId: resolved.taskId, skill: spec.skill, mode: spec.mode });
|
|
49
50
|
const identity = { taskId: resolved.taskId, taskDir: resolved.taskDir, taskState: resolved.state, skill: spec.skill, mode: spec.mode };
|
|
50
|
-
if (resolved.state !== spec.expectedState) {
|
|
51
|
+
if (resolved.state !== spec.expectedState && !allowsManualOverride(options.manualOverride, 'task-verify', 'VERIFY_TASK_STATE_MISMATCH')) {
|
|
51
52
|
return failure(request, 'VERIFY_TASK_STATE_MISMATCH', `event '${request.event}' requires workspace '${spec.expectedState}', received '${resolved.state}'`, identity);
|
|
52
53
|
}
|
|
53
54
|
if (spec.artifactFamily) {
|
|
@@ -66,12 +67,23 @@ function verifyTaskEvent(request, options = {}) {
|
|
|
66
67
|
if (spec.mode === 'gate') {
|
|
67
68
|
const payload = engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot });
|
|
68
69
|
const status = payload.gate;
|
|
70
|
+
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
71
|
+
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, gate: 'pass', humanOverride: 'human-approved', originalGate: status } });
|
|
72
|
+
return {
|
|
73
|
+
status: 'pass', changed: false, event: request.event, requestRef: request.taskRef,
|
|
74
|
+
...identity, artifact: request.artifact ?? null, invocations, error: null
|
|
75
|
+
};
|
|
76
|
+
}
|
|
69
77
|
invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
|
|
70
78
|
}
|
|
71
79
|
else {
|
|
72
80
|
for (const check of spec.checks ?? []) {
|
|
73
81
|
const payload = engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
|
|
74
82
|
const status = payload.status;
|
|
83
|
+
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
84
|
+
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
75
87
|
invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
|
|
76
88
|
if (status !== 'pass')
|
|
77
89
|
break;
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
3
3
|
import { WORKFLOW_WARNING_COLUMNS, WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_SEVERITIES, WORKFLOW_WARNING_STATUSES, parseWorkflowWarnings } from './workflow-warnings.js';
|
|
4
4
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
5
|
+
import { allowsManualOverride } from './guard-override.js';
|
|
5
6
|
function failed(intent, code, message, taskId = null, entityId = null) {
|
|
6
7
|
return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, warnings: [], operations: [], error: { code, message } };
|
|
7
8
|
}
|
|
@@ -54,7 +55,8 @@ function applyWorkflowWarningIntent(intent, options = {}) {
|
|
|
54
55
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
55
56
|
if (!resolved.ok)
|
|
56
57
|
return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
57
|
-
|
|
58
|
+
const stateOverride = allowsManualOverride(options.manualOverride, 'workflow-warning', 'TASK_STATE_MISMATCH');
|
|
59
|
+
if (resolved.state !== 'active' && !stateOverride)
|
|
58
60
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
59
61
|
let content;
|
|
60
62
|
let rows;
|
|
@@ -114,7 +116,7 @@ function applyWorkflowWarningIntent(intent, options = {}) {
|
|
|
114
116
|
after = { ...before, status: intent.status, resolvedAt: metadata.timestamp, resolution };
|
|
115
117
|
}
|
|
116
118
|
const writeResult = writeTask({
|
|
117
|
-
taskRef: intent.taskRef, expectedState: 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
|
|
119
|
+
taskRef: intent.taskRef, expectedState: stateOverride ? resolved.state : 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
|
|
118
120
|
}, {
|
|
119
121
|
...options,
|
|
120
122
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
package/dist/lib/task/write.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { VERSION } from '../version.js';
|
|
4
4
|
import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
|
|
5
5
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
6
|
+
import { allowsManualOverride } from './guard-override.js';
|
|
6
7
|
import { mutateTableRow, upsertSection } from './sections.js';
|
|
7
8
|
const DEFAULT_FILE_SYSTEM = {
|
|
8
9
|
readFileSync: (file) => fs.readFileSync(file, 'utf8'),
|
|
@@ -70,7 +71,7 @@ function writeTask(request, options = {}) {
|
|
|
70
71
|
taskMdPath: resolved.taskMdPath,
|
|
71
72
|
actualState: resolved.state
|
|
72
73
|
};
|
|
73
|
-
if (resolved.state !== request.expectedState) {
|
|
74
|
+
if (resolved.state !== request.expectedState && !allowsManualOverride(options.manualOverride, 'task.write', 'TASK_STATE_MISMATCH')) {
|
|
74
75
|
return failure(request, identity, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected ${request.expectedState}`);
|
|
75
76
|
}
|
|
76
77
|
const io = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
@@ -16,7 +16,7 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
16
16
|
commands: { level: 'integrated' },
|
|
17
17
|
hooks: { level: 'integrated' },
|
|
18
18
|
subagents: { level: 'experimental' },
|
|
19
|
-
orchestration: { level: '
|
|
19
|
+
orchestration: { level: 'experimental' },
|
|
20
20
|
sandbox: { level: 'integrated' },
|
|
21
21
|
verification: { level: 'compatible' }
|
|
22
22
|
},
|
|
@@ -26,8 +26,8 @@ const claudeCodeAdapter = defineAgentClientAdapter({
|
|
|
26
26
|
guidance: 'Use the host model picker for the complete model and reasoning-effort catalog.'
|
|
27
27
|
},
|
|
28
28
|
delegationEvidence: {
|
|
29
|
-
actualModel: '
|
|
30
|
-
actualReasoningEffort: '
|
|
29
|
+
actualModel: 'host-event',
|
|
30
|
+
actualReasoningEffort: 'spawn-ack'
|
|
31
31
|
},
|
|
32
32
|
project: {
|
|
33
33
|
ownedPathPrefixes: ['.claude/'],
|
|
@@ -7,6 +7,7 @@ import semver from 'semver';
|
|
|
7
7
|
|
|
8
8
|
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
9
9
|
import type { CodexLifecycleEvent } from './evidence.ts';
|
|
10
|
+
import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.ts';
|
|
10
11
|
|
|
11
12
|
type JsonObject = Record<string, unknown>;
|
|
12
13
|
type ThreadResolution = Readonly<{
|
|
@@ -19,6 +20,12 @@ type DiscoveredHook = Readonly<{
|
|
|
19
20
|
matcher: string | null;
|
|
20
21
|
command: string;
|
|
21
22
|
enabled: boolean;
|
|
23
|
+
source: string;
|
|
24
|
+
sourcePathDigest: string;
|
|
25
|
+
trustStatus: string;
|
|
26
|
+
currentHash: string;
|
|
27
|
+
isManaged: boolean;
|
|
28
|
+
pluginId: string | null;
|
|
22
29
|
}>;
|
|
23
30
|
|
|
24
31
|
type AppServerTransportOptions = Readonly<{
|
|
@@ -134,7 +141,11 @@ function validateCodexLifecycleHookConfig(value: unknown): void {
|
|
|
134
141
|
if (!valid) throw new Error('CODEX_PREFLIGHT_HOOKS_INVALID: lifecycle hooks are invalid');
|
|
135
142
|
}
|
|
136
143
|
|
|
137
|
-
function parseCodexHooksList(
|
|
144
|
+
function parseCodexHooksList(
|
|
145
|
+
value: unknown,
|
|
146
|
+
repoRoot: string,
|
|
147
|
+
expectedSource: 'direct-host' | 'isolated-user' = 'direct-host'
|
|
148
|
+
): readonly DiscoveredHook[] {
|
|
138
149
|
const root = object(value);
|
|
139
150
|
const entries = Array.isArray(root?.data) ? root.data : [];
|
|
140
151
|
const entry = entries.map(object).find((candidate) => candidate?.cwd === repoRoot);
|
|
@@ -147,13 +158,28 @@ function parseCodexHooksList(value: unknown, repoRoot: string): readonly Discove
|
|
|
147
158
|
eventName: String(hook.eventName ?? ''),
|
|
148
159
|
matcher: typeof hook.matcher === 'string' ? hook.matcher : null,
|
|
149
160
|
command: String(hook.command ?? ''),
|
|
150
|
-
enabled: hook.enabled === true
|
|
161
|
+
enabled: hook.enabled === true,
|
|
162
|
+
source: String(hook.source ?? ''),
|
|
163
|
+
sourcePathDigest: typeof hook.sourcePath === 'string' && path.isAbsolute(hook.sourcePath)
|
|
164
|
+
? crypto.createHash('sha256').update(hook.sourcePath).digest('hex')
|
|
165
|
+
: '',
|
|
166
|
+
trustStatus: String(hook.trustStatus ?? ''),
|
|
167
|
+
currentHash: String(hook.currentHash ?? ''),
|
|
168
|
+
isManaged: hook.isManaged === true,
|
|
169
|
+
pluginId: typeof hook.pluginId === 'string' ? hook.pluginId : null
|
|
151
170
|
}));
|
|
152
171
|
const valid = LIFECYCLE_HOOKS.every(({ event, matcher, phase }) => hooks.some((hook) =>
|
|
153
172
|
hook.eventName === event.replace(/^./, (character) => character.toLowerCase())
|
|
154
173
|
&& hook.matcher === matcher
|
|
155
174
|
&& hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`
|
|
156
175
|
&& hook.enabled
|
|
176
|
+
&& hook.currentHash.length > 0
|
|
177
|
+
&& hook.sourcePathDigest.length === 64
|
|
178
|
+
&& hook.pluginId === null
|
|
179
|
+
&& (expectedSource === 'isolated-user'
|
|
180
|
+
? hook.source === 'user' && hook.trustStatus !== 'modified'
|
|
181
|
+
: (hook.source === 'project' && hook.trustStatus === 'trusted')
|
|
182
|
+
|| hook.isManaged && hook.trustStatus === 'managed')
|
|
157
183
|
));
|
|
158
184
|
if (!valid) throw new Error('CODEX_PREFLIGHT_HOOKS_NOT_LOADED: lifecycle hooks are not loaded for this workspace');
|
|
159
185
|
return Object.freeze(hooks);
|
|
@@ -533,7 +559,7 @@ async function preflightCodexLifecycleEvidence(
|
|
|
533
559
|
}
|
|
534
560
|
|
|
535
561
|
const hookDefinitionHash = crypto.createHash('sha256').update(hooksRaw).digest('hex');
|
|
536
|
-
const runtimeRoot =
|
|
562
|
+
const runtimeRoot = resolveAgentRuntimeStoreRoot({ repoRoot, store: 'lifecycle' });
|
|
537
563
|
const runtimeLiveness = hasCodexRuntimeLiveness(runtimeRoot, hookDefinitionHash, runtimeIdentity);
|
|
538
564
|
|
|
539
565
|
const transport = new CodexAppServerTransport();
|
|
@@ -541,13 +567,45 @@ async function preflightCodexLifecycleEvidence(
|
|
|
541
567
|
let discoveredHooks: readonly DiscoveredHook[];
|
|
542
568
|
try {
|
|
543
569
|
await transport.initialize();
|
|
544
|
-
discoveredHooks = parseCodexHooksList(
|
|
570
|
+
discoveredHooks = parseCodexHooksList(
|
|
571
|
+
await transport.request('hooks/list', { cwds: [repoRoot] }),
|
|
572
|
+
repoRoot,
|
|
573
|
+
process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT ? 'isolated-user' : 'direct-host'
|
|
574
|
+
);
|
|
545
575
|
} finally { transport.close(); }
|
|
576
|
+
const lifecycleHooks = LIFECYCLE_HOOKS.map(({ event, matcher, phase }) => discoveredHooks.filter((hook) =>
|
|
577
|
+
hook.eventName === event.replace(/^./, (character) => character.toLowerCase())
|
|
578
|
+
&& hook.matcher === matcher
|
|
579
|
+
&& hook.command === `node "$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js" --client codex --event ${phase}`
|
|
580
|
+
));
|
|
581
|
+
if (lifecycleHooks.some((matches) => matches.length !== 1)) {
|
|
582
|
+
throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hook source is not unique');
|
|
583
|
+
}
|
|
584
|
+
const matchedHooks = lifecycleHooks.flat();
|
|
585
|
+
const sources = new Set(matchedHooks.map((hook) => hook.isManaged ? 'managed' : hook.source));
|
|
586
|
+
const sourcePathDigests = new Set(matchedHooks.map((hook) => hook.sourcePathDigest));
|
|
587
|
+
if (sources.size !== 1 || sourcePathDigests.size !== 1) {
|
|
588
|
+
throw new Error('CODEX_PREFLIGHT_HOOK_SOURCE_AMBIGUOUS: lifecycle hooks do not share one exact source');
|
|
589
|
+
}
|
|
590
|
+
const source = [...sources][0];
|
|
591
|
+
const hookSource = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT
|
|
592
|
+
? 'isolated-user'
|
|
593
|
+
: source === 'managed' ? 'managed' : 'project';
|
|
546
594
|
return Object.freeze({
|
|
547
595
|
cliVersion: match[1],
|
|
548
596
|
hookDefinitionHash,
|
|
549
597
|
staticReady: true,
|
|
550
598
|
discoveredHooks,
|
|
599
|
+
hookProvenance: Object.freeze({
|
|
600
|
+
hookSource,
|
|
601
|
+
hookSourcePathDigest: [...sourcePathDigests][0]!,
|
|
602
|
+
hookSourceHash: crypto.createHash('sha256').update(JSON.stringify(matchedHooks.map((hook) => ({
|
|
603
|
+
eventName: hook.eventName,
|
|
604
|
+
matcher: hook.matcher,
|
|
605
|
+
command: hook.command,
|
|
606
|
+
currentHash: hook.currentHash
|
|
607
|
+
})))).digest('hex')
|
|
608
|
+
}),
|
|
551
609
|
runtimeLiveness,
|
|
552
610
|
diagnostics: transport.diagnostics
|
|
553
611
|
});
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const LIFECYCLE_PROTOCOL_VERSION = 3;
|
|
7
|
+
|
|
8
|
+
type LifecycleBuildIdentity = Readonly<{
|
|
9
|
+
protocolVersion: 3;
|
|
10
|
+
packageVersion: string;
|
|
11
|
+
internalExecutableBuildHash: string;
|
|
12
|
+
lifecycleContractHash: string;
|
|
13
|
+
}>;
|
|
14
|
+
|
|
15
|
+
type LifecycleBuildIdentityOptions = Readonly<{
|
|
16
|
+
executableFiles?: readonly string[];
|
|
17
|
+
contractFiles?: readonly string[];
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
type LifecycleManifestFiles = Readonly<{
|
|
21
|
+
executableFiles: readonly string[];
|
|
22
|
+
contractFiles: readonly string[];
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
type IdentityVerification = Readonly<{
|
|
26
|
+
ok: boolean;
|
|
27
|
+
code: string | null;
|
|
28
|
+
message: string | null;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
function exactText(value: unknown): value is string {
|
|
32
|
+
return typeof value === 'string' && value.trim() === value && value.length > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeRelativePath(value: string): string {
|
|
36
|
+
return value.replaceAll('\\', '/');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readLifecycleManifestFiles(packageRoot: string): LifecycleManifestFiles {
|
|
40
|
+
const file = path.join(
|
|
41
|
+
packageRoot,
|
|
42
|
+
'lib',
|
|
43
|
+
'agent-clients',
|
|
44
|
+
'adapters',
|
|
45
|
+
'codex-lifecycle',
|
|
46
|
+
'manifest-files.json'
|
|
47
|
+
);
|
|
48
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8')) as {
|
|
49
|
+
executableFiles?: unknown;
|
|
50
|
+
contractFiles?: unknown;
|
|
51
|
+
};
|
|
52
|
+
if (!Array.isArray(value.executableFiles)
|
|
53
|
+
|| !Array.isArray(value.contractFiles)
|
|
54
|
+
|| !value.executableFiles.every(exactText)
|
|
55
|
+
|| !value.contractFiles.every(exactText)
|
|
56
|
+
|| new Set(value.executableFiles).size !== value.executableFiles.length
|
|
57
|
+
|| new Set(value.contractFiles).size !== value.contractFiles.length) {
|
|
58
|
+
throw new Error('Lifecycle manifest file list is invalid');
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
executableFiles: Object.freeze([...value.executableFiles] as string[]),
|
|
62
|
+
contractFiles: Object.freeze([...value.contractFiles] as string[])
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function executablePackageRoot(): string {
|
|
67
|
+
let current = path.dirname(fileURLToPath(import.meta.url));
|
|
68
|
+
while (true) {
|
|
69
|
+
const candidate = path.join(current, 'package.json');
|
|
70
|
+
if (fs.existsSync(candidate)) {
|
|
71
|
+
const value = JSON.parse(fs.readFileSync(candidate, 'utf8')) as { name?: unknown };
|
|
72
|
+
if (value.name === '@fitlab-ai/agent-infra') return current;
|
|
73
|
+
}
|
|
74
|
+
const parent = path.dirname(current);
|
|
75
|
+
if (parent === current) throw new Error('Lifecycle executable package root is unavailable');
|
|
76
|
+
current = parent;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hashFiles(root: string, files: readonly string[]): string {
|
|
81
|
+
const hash = crypto.createHash('sha256');
|
|
82
|
+
for (const relative of [...new Set(files.map(normalizeRelativePath))].sort()) {
|
|
83
|
+
if (path.isAbsolute(relative) || relative.split(/[\\/]/u).includes('..')) {
|
|
84
|
+
throw new Error(`Lifecycle manifest path '${relative}' is unsafe`);
|
|
85
|
+
}
|
|
86
|
+
const file = path.join(root, relative);
|
|
87
|
+
const stat = fs.lstatSync(file);
|
|
88
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
89
|
+
throw new Error(`Lifecycle manifest entry '${relative}' must be a regular file`);
|
|
90
|
+
}
|
|
91
|
+
hash.update(relative);
|
|
92
|
+
hash.update('\0');
|
|
93
|
+
hash.update(fs.readFileSync(file));
|
|
94
|
+
hash.update('\0');
|
|
95
|
+
}
|
|
96
|
+
return hash.digest('hex');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveLifecycleExecutableFiles(root: string, entries: readonly string[]): string[] {
|
|
100
|
+
const pending = [...entries];
|
|
101
|
+
const resolved = new Set<string>();
|
|
102
|
+
while (pending.length > 0) {
|
|
103
|
+
const relative = normalizeRelativePath(pending.pop()!);
|
|
104
|
+
if (resolved.has(relative)) continue;
|
|
105
|
+
resolved.add(relative);
|
|
106
|
+
if (!/\.[cm]?[jt]s$/u.test(relative)) continue;
|
|
107
|
+
const file = path.join(root, relative);
|
|
108
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
109
|
+
const imports = source.matchAll(/(?:\bfrom\s*|\bimport\s*(?:\(\s*)?)["'](\.[^"']+)["']/gu);
|
|
110
|
+
for (const match of imports) {
|
|
111
|
+
const requested = match[1]!;
|
|
112
|
+
const base = path.resolve(path.dirname(file), requested);
|
|
113
|
+
const candidates = [
|
|
114
|
+
base,
|
|
115
|
+
`${base}.ts`, `${base}.js`,
|
|
116
|
+
path.join(base, 'index.ts'), path.join(base, 'index.js')
|
|
117
|
+
];
|
|
118
|
+
const dependency = candidates.find((candidate) => fs.existsSync(candidate) && fs.lstatSync(candidate).isFile());
|
|
119
|
+
if (!dependency) throw new Error(`Lifecycle executable dependency '${requested}' from '${relative}' is unavailable`);
|
|
120
|
+
const dependencyRelative = path.relative(root, dependency);
|
|
121
|
+
if (dependencyRelative.startsWith('..') || path.isAbsolute(dependencyRelative)) {
|
|
122
|
+
throw new Error(`Lifecycle executable dependency '${requested}' escapes the package root`);
|
|
123
|
+
}
|
|
124
|
+
pending.push(normalizeRelativePath(dependencyRelative));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return [...resolved].sort();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function computeLifecycleBuildIdentity(
|
|
131
|
+
repoRoot: string,
|
|
132
|
+
options: LifecycleBuildIdentityOptions = {}
|
|
133
|
+
): LifecycleBuildIdentity {
|
|
134
|
+
const packageRoot = executablePackageRoot();
|
|
135
|
+
const executableRoot = options.executableFiles ? repoRoot : packageRoot;
|
|
136
|
+
const defaults = readLifecycleManifestFiles(packageRoot);
|
|
137
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(executableRoot, 'package.json'), 'utf8')) as {
|
|
138
|
+
version?: unknown;
|
|
139
|
+
};
|
|
140
|
+
if (!exactText(packageJson.version)) throw new Error('Lifecycle package version is invalid');
|
|
141
|
+
let internalExecutableBuildHash: string;
|
|
142
|
+
if (options.executableFiles) {
|
|
143
|
+
internalExecutableBuildHash = hashFiles(repoRoot, options.executableFiles);
|
|
144
|
+
} else {
|
|
145
|
+
const manifestFile = path.join(executableRoot, 'dist', 'lifecycle-build-manifest.json');
|
|
146
|
+
if (fs.existsSync(manifestFile)) {
|
|
147
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8')) as {
|
|
148
|
+
protocolVersion?: unknown;
|
|
149
|
+
packageVersion?: unknown;
|
|
150
|
+
sourceInputHash?: unknown;
|
|
151
|
+
internalExecutableBuildHash?: unknown;
|
|
152
|
+
};
|
|
153
|
+
if (manifest.protocolVersion !== LIFECYCLE_PROTOCOL_VERSION
|
|
154
|
+
|| manifest.packageVersion !== packageJson.version
|
|
155
|
+
|| !exactText(manifest.sourceInputHash)
|
|
156
|
+
|| !exactText(manifest.internalExecutableBuildHash)) {
|
|
157
|
+
throw new Error('Lifecycle executable build manifest is stale or invalid');
|
|
158
|
+
}
|
|
159
|
+
if (fileURLToPath(import.meta.url).split(path.sep).includes('dist')) {
|
|
160
|
+
const compiledFiles = defaults.executableFiles.map((file) =>
|
|
161
|
+
file.endsWith('.ts') ? path.join('dist', file.replace(/\.ts$/u, '.js')) : file
|
|
162
|
+
);
|
|
163
|
+
if (hashFiles(executableRoot, resolveLifecycleExecutableFiles(executableRoot, compiledFiles)) !== manifest.internalExecutableBuildHash) {
|
|
164
|
+
throw new Error('Lifecycle compiled executable build does not match its manifest');
|
|
165
|
+
}
|
|
166
|
+
} else if (hashFiles(executableRoot, resolveLifecycleExecutableFiles(executableRoot, defaults.executableFiles)) !== manifest.sourceInputHash) {
|
|
167
|
+
throw new Error('Lifecycle executable build manifest is stale or invalid');
|
|
168
|
+
}
|
|
169
|
+
internalExecutableBuildHash = manifest.internalExecutableBuildHash;
|
|
170
|
+
} else internalExecutableBuildHash = hashFiles(
|
|
171
|
+
executableRoot,
|
|
172
|
+
resolveLifecycleExecutableFiles(executableRoot, defaults.executableFiles)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return Object.freeze({
|
|
176
|
+
protocolVersion: LIFECYCLE_PROTOCOL_VERSION,
|
|
177
|
+
packageVersion: packageJson.version,
|
|
178
|
+
internalExecutableBuildHash,
|
|
179
|
+
lifecycleContractHash: hashFiles(
|
|
180
|
+
repoRoot,
|
|
181
|
+
options.contractFiles ?? defaults.contractFiles
|
|
182
|
+
)
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function verifyLifecycleBuildIdentity(
|
|
187
|
+
expected: LifecycleBuildIdentity,
|
|
188
|
+
actual: LifecycleBuildIdentity
|
|
189
|
+
): IdentityVerification {
|
|
190
|
+
if (actual.protocolVersion !== expected.protocolVersion) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
code: 'CODEX_LIFECYCLE_PROTOCOL_MISMATCH',
|
|
194
|
+
message: 'Codex lifecycle protocol version does not match'
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (actual.packageVersion !== expected.packageVersion
|
|
198
|
+
|| actual.internalExecutableBuildHash !== expected.internalExecutableBuildHash) {
|
|
199
|
+
return {
|
|
200
|
+
ok: false,
|
|
201
|
+
code: 'CODEX_LIFECYCLE_BUILD_MISMATCH',
|
|
202
|
+
message: 'Codex lifecycle executable build identity does not match'
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (actual.lifecycleContractHash !== expected.lifecycleContractHash) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
code: 'CODEX_LIFECYCLE_CONTRACT_MISMATCH',
|
|
209
|
+
message: 'Codex lifecycle contract identity does not match'
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return { ok: true, code: null, message: null };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export {
|
|
216
|
+
LIFECYCLE_PROTOCOL_VERSION,
|
|
217
|
+
computeLifecycleBuildIdentity,
|
|
218
|
+
readLifecycleManifestFiles,
|
|
219
|
+
resolveLifecycleExecutableFiles,
|
|
220
|
+
verifyLifecycleBuildIdentity
|
|
221
|
+
};
|
|
222
|
+
export type {
|
|
223
|
+
IdentityVerification,
|
|
224
|
+
LifecycleBuildIdentity,
|
|
225
|
+
LifecycleManifestFiles,
|
|
226
|
+
LifecycleBuildIdentityOptions
|
|
227
|
+
};
|