@fitlab-ai/agent-infra 0.9.14 → 0.10.0
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 +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -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 +4 -2
- 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 +331 -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 +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- 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 +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- 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 +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- 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 +79 -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 +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- 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 +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -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-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- 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 +105 -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 +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -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/lib/version.js +1 -11
- 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 +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -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 +4 -2
- 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 +315 -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 +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- 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 +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- 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 +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- 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 +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- 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 +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -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-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- 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 +156 -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 +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -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/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- 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/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- 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 +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +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/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -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/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- 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/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- 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,304 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
|
|
5
|
+
import { validateManualValidationReceipt } from './manual-validation-receipt.ts';
|
|
6
|
+
import {
|
|
7
|
+
MANUAL_VALIDATION_ARTIFACT as ARTIFACT,
|
|
8
|
+
MANUAL_VALIDATION_SHA40 as SHA40,
|
|
9
|
+
MANUAL_VALIDATION_SHA64 as SHA64,
|
|
10
|
+
MANUAL_VALIDATION_TASK_ID as TASK_ID,
|
|
11
|
+
exactKeys,
|
|
12
|
+
isRecord,
|
|
13
|
+
validTimestamp,
|
|
14
|
+
writeJsonAtomic
|
|
15
|
+
} from './manual-validation-shared.ts';
|
|
16
|
+
|
|
17
|
+
const MANUAL_VALIDATION_TRANSACTION_SCHEMA = 'agent-infra/manual-validation-transaction';
|
|
18
|
+
const MANUAL_VALIDATION_TRANSACTION_VERSION = 1;
|
|
19
|
+
const TRANSACTION_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
20
|
+
|
|
21
|
+
type ManualValidationTransactionPhase = 'prepared' | 'summary-staged' | 'receipt-committed' | 'final-promotion-in-progress' | 'committed' | 'aborted' | 'recovery-required';
|
|
22
|
+
type ManualValidationSummaryPreimage = Readonly<{ commentId: number | string | null; body: string; digest: string }>;
|
|
23
|
+
type ManualValidationTransaction = Readonly<{
|
|
24
|
+
schema: typeof MANUAL_VALIDATION_TRANSACTION_SCHEMA;
|
|
25
|
+
version: typeof MANUAL_VALIDATION_TRANSACTION_VERSION;
|
|
26
|
+
transactionId: string;
|
|
27
|
+
taskId: string;
|
|
28
|
+
prNumber: number;
|
|
29
|
+
prHeadSha: string;
|
|
30
|
+
evidenceDigest: string;
|
|
31
|
+
summaryPreimage: ManualValidationSummaryPreimage;
|
|
32
|
+
pendingSummaryDigest: string;
|
|
33
|
+
finalSummaryDigest: string;
|
|
34
|
+
artifact: string;
|
|
35
|
+
phase: ManualValidationTransactionPhase;
|
|
36
|
+
committedReceipt: string | null;
|
|
37
|
+
eventAppended: boolean;
|
|
38
|
+
attempt: number;
|
|
39
|
+
postWriteVerified: boolean;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
error: string | null;
|
|
43
|
+
}>;
|
|
44
|
+
type ManualValidationTransactionInput = Omit<ManualValidationTransaction, 'schema' | 'version' | 'phase' | 'committedReceipt' | 'eventAppended' | 'postWriteVerified' | 'error'> & {
|
|
45
|
+
transactionId?: string;
|
|
46
|
+
};
|
|
47
|
+
type ManualValidationTransactionPatch = Partial<Pick<ManualValidationTransaction, 'committedReceipt' | 'eventAppended' | 'postWriteVerified' | 'error' | 'updatedAt' | 'attempt'>>;
|
|
48
|
+
type ManualValidationTransactionErrorCode =
|
|
49
|
+
| 'MANUAL_VALIDATION_TRANSACTION_INVALID'
|
|
50
|
+
| 'MANUAL_VALIDATION_TRANSACTION_MISSING'
|
|
51
|
+
| 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH'
|
|
52
|
+
| 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID';
|
|
53
|
+
type ManualValidationTransactionError = { code: ManualValidationTransactionErrorCode; message: string };
|
|
54
|
+
type ManualValidationTransactionResult =
|
|
55
|
+
| { ok: true; value: ManualValidationTransaction }
|
|
56
|
+
| { ok: false; error: ManualValidationTransactionError };
|
|
57
|
+
type ManualValidationGenerationArchiveOptions = Readonly<{
|
|
58
|
+
afterReceiptMove?: () => void;
|
|
59
|
+
}>;
|
|
60
|
+
|
|
61
|
+
function invalid(message: string): ManualValidationTransactionResult {
|
|
62
|
+
return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_INVALID', message } };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function summaryPreimageDigest(body: string): string {
|
|
66
|
+
return createHash('sha256').update(body, 'utf8').digest('hex');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateManualValidationTransaction(value: unknown, expected?: Partial<Pick<ManualValidationTransaction, 'transactionId' | 'taskId' | 'prNumber' | 'prHeadSha' | 'evidenceDigest' | 'artifact'>>): ManualValidationTransactionResult {
|
|
70
|
+
if (!isRecord(value)) return invalid('transaction must be a JSON object');
|
|
71
|
+
const keys = ['schema', 'version', 'transactionId', 'taskId', 'prNumber', 'prHeadSha', 'evidenceDigest', 'summaryPreimage', 'pendingSummaryDigest', 'finalSummaryDigest', 'artifact', 'phase', 'committedReceipt', 'eventAppended', 'attempt', 'postWriteVerified', 'createdAt', 'updatedAt', 'error'];
|
|
72
|
+
const keyError = exactKeys(value, keys, 'transaction');
|
|
73
|
+
if (keyError) return invalid(keyError);
|
|
74
|
+
if (value.schema !== MANUAL_VALIDATION_TRANSACTION_SCHEMA || value.version !== MANUAL_VALIDATION_TRANSACTION_VERSION) return invalid('transaction schema or version is unsupported');
|
|
75
|
+
if (typeof value.transactionId !== 'string' || !TRANSACTION_ID.test(value.transactionId)) return invalid('transactionId is invalid');
|
|
76
|
+
if (typeof value.taskId !== 'string' || !TASK_ID.test(value.taskId)) return invalid('taskId is invalid');
|
|
77
|
+
const prNumber = value.prNumber;
|
|
78
|
+
if (!Number.isSafeInteger(prNumber) || (prNumber as number) <= 0) return invalid('prNumber is invalid');
|
|
79
|
+
if (typeof value.prHeadSha !== 'string' || !SHA40.test(value.prHeadSha)) return invalid('prHeadSha is invalid');
|
|
80
|
+
if (typeof value.evidenceDigest !== 'string' || !SHA64.test(value.evidenceDigest)) return invalid('evidenceDigest is invalid');
|
|
81
|
+
if (!isRecord(value.summaryPreimage)) return invalid('summaryPreimage is invalid');
|
|
82
|
+
const preimage = value.summaryPreimage;
|
|
83
|
+
const preimageKeyError = exactKeys(preimage, ['commentId', 'body', 'digest'], 'summaryPreimage');
|
|
84
|
+
if (preimageKeyError) return invalid(preimageKeyError);
|
|
85
|
+
if (preimage.commentId !== null && typeof preimage.commentId !== 'number' && typeof preimage.commentId !== 'string') return invalid('summaryPreimage.commentId is invalid');
|
|
86
|
+
if (typeof preimage.body !== 'string' || typeof preimage.digest !== 'string' || !SHA64.test(preimage.digest) || summaryPreimageDigest(preimage.body) !== preimage.digest) return invalid('summaryPreimage digest is invalid');
|
|
87
|
+
for (const field of ['pendingSummaryDigest', 'finalSummaryDigest'] as const) if (typeof value[field] !== 'string' || !SHA64.test(value[field])) return invalid(`${field} is invalid`);
|
|
88
|
+
if (typeof value.artifact !== 'string' || !ARTIFACT.test(value.artifact)) return invalid('artifact is invalid');
|
|
89
|
+
if (!['prepared', 'summary-staged', 'receipt-committed', 'final-promotion-in-progress', 'committed', 'aborted', 'recovery-required'].includes(value.phase as string)) return invalid('phase is invalid');
|
|
90
|
+
if (value.committedReceipt !== null && (typeof value.committedReceipt !== 'string' || !SHA64.test(value.committedReceipt))) return invalid('committedReceipt is invalid');
|
|
91
|
+
if (typeof value.eventAppended !== 'boolean' || typeof value.postWriteVerified !== 'boolean') return invalid('transaction completion flags are invalid');
|
|
92
|
+
const attempt = value.attempt;
|
|
93
|
+
if (!Number.isSafeInteger(attempt) || (attempt as number) < 1) return invalid('attempt is invalid');
|
|
94
|
+
if (!validTimestamp(value.createdAt) || !validTimestamp(value.updatedAt)) return invalid('transaction timestamps are invalid');
|
|
95
|
+
if (value.error !== null && (typeof value.error !== 'string' || !value.error || /[\r\n]/u.test(value.error))) return invalid('transaction error is invalid');
|
|
96
|
+
if (['receipt-committed', 'final-promotion-in-progress', 'committed'].includes(value.phase as string) && value.committedReceipt === null) return invalid('receipt-backed phases require committedReceipt');
|
|
97
|
+
if (['final-promotion-in-progress', 'committed'].includes(value.phase as string) && !value.eventAppended) return invalid('final promotion requires an appended completion event');
|
|
98
|
+
if (value.phase === 'committed' && !value.postWriteVerified) return invalid('committed phase requires post-write verification');
|
|
99
|
+
const transaction = value as unknown as ManualValidationTransaction;
|
|
100
|
+
if (expected && Object.entries(expected).some(([key, expectedValue]) => expectedValue !== undefined && transaction[key as keyof ManualValidationTransaction] !== expectedValue)) {
|
|
101
|
+
return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH', message: 'transaction identity does not match the expected operation' } };
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, value: transaction };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createManualValidationTransaction(input: ManualValidationTransactionInput): ManualValidationTransaction {
|
|
107
|
+
const transaction: ManualValidationTransaction = {
|
|
108
|
+
schema: MANUAL_VALIDATION_TRANSACTION_SCHEMA,
|
|
109
|
+
version: MANUAL_VALIDATION_TRANSACTION_VERSION,
|
|
110
|
+
transactionId: input.transactionId ?? `mv-${randomUUID()}`,
|
|
111
|
+
taskId: input.taskId,
|
|
112
|
+
prNumber: input.prNumber,
|
|
113
|
+
prHeadSha: input.prHeadSha,
|
|
114
|
+
evidenceDigest: input.evidenceDigest,
|
|
115
|
+
summaryPreimage: input.summaryPreimage,
|
|
116
|
+
pendingSummaryDigest: input.pendingSummaryDigest,
|
|
117
|
+
finalSummaryDigest: input.finalSummaryDigest,
|
|
118
|
+
artifact: input.artifact,
|
|
119
|
+
phase: 'prepared',
|
|
120
|
+
committedReceipt: null,
|
|
121
|
+
eventAppended: false,
|
|
122
|
+
attempt: input.attempt,
|
|
123
|
+
postWriteVerified: false,
|
|
124
|
+
createdAt: input.createdAt,
|
|
125
|
+
updatedAt: input.updatedAt,
|
|
126
|
+
error: null
|
|
127
|
+
};
|
|
128
|
+
const checked = validateManualValidationTransaction(transaction);
|
|
129
|
+
if (!checked.ok) throw new Error(`${checked.error.code}: ${checked.error.message}`);
|
|
130
|
+
return checked.value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const allowedTransitions: Record<ManualValidationTransactionPhase, readonly ManualValidationTransactionPhase[]> = {
|
|
134
|
+
prepared: ['prepared', 'summary-staged', 'aborted', 'recovery-required'],
|
|
135
|
+
'summary-staged': ['summary-staged', 'receipt-committed', 'aborted', 'recovery-required'],
|
|
136
|
+
'receipt-committed': ['receipt-committed', 'final-promotion-in-progress', 'aborted', 'recovery-required'],
|
|
137
|
+
'final-promotion-in-progress': ['final-promotion-in-progress', 'committed', 'aborted', 'recovery-required'],
|
|
138
|
+
committed: ['committed'],
|
|
139
|
+
aborted: ['aborted', 'prepared'],
|
|
140
|
+
'recovery-required': ['recovery-required', 'prepared']
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
function transitionManualValidationTransaction(
|
|
144
|
+
current: ManualValidationTransaction,
|
|
145
|
+
phase: ManualValidationTransactionPhase,
|
|
146
|
+
patch: ManualValidationTransactionPatch = {}
|
|
147
|
+
): ManualValidationTransactionResult {
|
|
148
|
+
if (!allowedTransitions[current.phase].includes(phase)) return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: `cannot transition from ${current.phase} to ${phase}` } };
|
|
149
|
+
if (phase === 'final-promotion-in-progress' && (!current.eventAppended && patch.eventAppended !== true)) return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final promotion requires an appended completion event' } };
|
|
150
|
+
const next: ManualValidationTransaction = {
|
|
151
|
+
...current,
|
|
152
|
+
phase,
|
|
153
|
+
committedReceipt: patch.committedReceipt !== undefined ? patch.committedReceipt : current.committedReceipt,
|
|
154
|
+
eventAppended: patch.eventAppended ?? current.eventAppended,
|
|
155
|
+
postWriteVerified: patch.postWriteVerified ?? current.postWriteVerified,
|
|
156
|
+
attempt: patch.attempt ?? current.attempt,
|
|
157
|
+
error: patch.error !== undefined ? patch.error : current.error,
|
|
158
|
+
updatedAt: patch.updatedAt ?? new Date().toISOString()
|
|
159
|
+
};
|
|
160
|
+
const checked = validateManualValidationTransaction(next);
|
|
161
|
+
if (!checked.ok) return checked;
|
|
162
|
+
return checked;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function retryManualValidationTransaction(current: ManualValidationTransaction, now = new Date().toISOString()): ManualValidationTransactionResult {
|
|
166
|
+
if (!['aborted', 'recovery-required'].includes(current.phase)) return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'only aborted or recovery-required transactions can be retried' } };
|
|
167
|
+
return transitionManualValidationTransaction(current, 'prepared', {
|
|
168
|
+
updatedAt: now,
|
|
169
|
+
error: null,
|
|
170
|
+
committedReceipt: null,
|
|
171
|
+
eventAppended: false,
|
|
172
|
+
postWriteVerified: false,
|
|
173
|
+
attempt: current.attempt + 1
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function manualValidationTransactionPath(taskDir: string): string {
|
|
178
|
+
return path.join(taskDir, '.manual-validation', 'transaction.json');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function manualValidationGenerationPaths(taskDir: string, transaction: ManualValidationTransaction): {
|
|
182
|
+
receipt: { current: string; history: string };
|
|
183
|
+
transaction: { current: string; history: string };
|
|
184
|
+
} {
|
|
185
|
+
const stateDir = path.join(taskDir, '.manual-validation');
|
|
186
|
+
const historyDir = path.join(stateDir, 'history');
|
|
187
|
+
return {
|
|
188
|
+
receipt: {
|
|
189
|
+
current: path.join(stateDir, 'receipt.json'),
|
|
190
|
+
history: path.join(historyDir, `receipt-${transaction.transactionId}-attempt-${transaction.attempt}.json`)
|
|
191
|
+
},
|
|
192
|
+
transaction: {
|
|
193
|
+
current: manualValidationTransactionPath(taskDir),
|
|
194
|
+
history: path.join(historyDir, `transaction-${transaction.transactionId}-attempt-${transaction.attempt}.json`)
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function readReceiptAt(file: string, transaction: ManualValidationTransaction): 'missing' | 'valid' {
|
|
200
|
+
let value: unknown;
|
|
201
|
+
try {
|
|
202
|
+
value = JSON.parse(fs.readFileSync(file, 'utf8')) as unknown;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return 'missing';
|
|
205
|
+
throw new Error(`manual-validation receipt is unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
}
|
|
207
|
+
const checked = validateManualValidationReceipt(value, {
|
|
208
|
+
transactionId: transaction.transactionId,
|
|
209
|
+
taskId: transaction.taskId,
|
|
210
|
+
prNumber: transaction.prNumber,
|
|
211
|
+
prHeadSha: transaction.prHeadSha,
|
|
212
|
+
evidenceDigest: transaction.evidenceDigest,
|
|
213
|
+
artifact: transaction.artifact
|
|
214
|
+
});
|
|
215
|
+
if (!checked.ok) throw new Error(`manual-validation receipt is invalid: ${checked.error.message}`);
|
|
216
|
+
if (transaction.committedReceipt !== null && checked.value.receiptDigest !== transaction.committedReceipt) {
|
|
217
|
+
throw new Error('manual-validation receipt does not match the committed transaction');
|
|
218
|
+
}
|
|
219
|
+
return 'valid';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function validateManualValidationGenerationArchive(taskDir: string, transaction: ManualValidationTransaction, requireReceipt = false): void {
|
|
223
|
+
const paths = manualValidationGenerationPaths(taskDir, transaction);
|
|
224
|
+
const currentTransaction = fs.existsSync(paths.transaction.current);
|
|
225
|
+
const archivedTransaction = fs.existsSync(paths.transaction.history);
|
|
226
|
+
if (currentTransaction && archivedTransaction) throw new Error(`manual-validation archive has conflicting transaction files for ${transaction.transactionId}`);
|
|
227
|
+
if (!currentTransaction && !archivedTransaction) throw new Error(`manual-validation archive source is missing: ${paths.transaction.current}`);
|
|
228
|
+
const currentReceipt = requireReceipt
|
|
229
|
+
? readReceiptAt(paths.receipt.current, transaction)
|
|
230
|
+
: (fs.existsSync(paths.receipt.current) ? 'valid' : 'missing');
|
|
231
|
+
const archivedReceipt = requireReceipt
|
|
232
|
+
? readReceiptAt(paths.receipt.history, transaction)
|
|
233
|
+
: (fs.existsSync(paths.receipt.history) ? 'valid' : 'missing');
|
|
234
|
+
if (requireReceipt && currentReceipt === 'missing' && archivedReceipt === 'missing') {
|
|
235
|
+
throw new Error(`manual-validation receipt is missing for transaction ${transaction.transactionId}`);
|
|
236
|
+
}
|
|
237
|
+
if (currentReceipt === 'valid' && archivedReceipt === 'valid') {
|
|
238
|
+
throw new Error(`manual-validation archive has conflicting receipt files for ${transaction.transactionId}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function archiveManualValidationGeneration(
|
|
243
|
+
taskDir: string,
|
|
244
|
+
transaction: ManualValidationTransaction,
|
|
245
|
+
requireReceipt = false,
|
|
246
|
+
options: ManualValidationGenerationArchiveOptions = {}
|
|
247
|
+
): void {
|
|
248
|
+
const paths = manualValidationGenerationPaths(taskDir, transaction);
|
|
249
|
+
validateManualValidationGenerationArchive(taskDir, transaction, requireReceipt);
|
|
250
|
+
fs.mkdirSync(path.dirname(paths.receipt.history), { recursive: true });
|
|
251
|
+
const move = (source: string, target: string, required: boolean): void => {
|
|
252
|
+
if (!fs.existsSync(source)) {
|
|
253
|
+
if (fs.existsSync(target) || !required) return;
|
|
254
|
+
throw new Error(`manual-validation archive source is missing: ${source}`);
|
|
255
|
+
}
|
|
256
|
+
if (fs.existsSync(target)) throw new Error(`manual-validation archive target already exists: ${target}`);
|
|
257
|
+
fs.renameSync(source, target);
|
|
258
|
+
};
|
|
259
|
+
move(paths.receipt.current, paths.receipt.history, requireReceipt);
|
|
260
|
+
options.afterReceiptMove?.();
|
|
261
|
+
move(paths.transaction.current, paths.transaction.history, true);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function writeManualValidationTransactionAtomic(taskDir: string, transaction: ManualValidationTransaction): string {
|
|
265
|
+
const checked = validateManualValidationTransaction(transaction);
|
|
266
|
+
if (!checked.ok) throw new Error(`${checked.error.code}: ${checked.error.message}`);
|
|
267
|
+
const target = manualValidationTransactionPath(taskDir);
|
|
268
|
+
return writeJsonAtomic(target, transaction);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function readManualValidationTransaction(taskDir: string, expected?: Parameters<typeof validateManualValidationTransaction>[1]): ManualValidationTransactionResult {
|
|
272
|
+
const file = manualValidationTransactionPath(taskDir);
|
|
273
|
+
let value: unknown;
|
|
274
|
+
try { value = JSON.parse(fs.readFileSync(file, 'utf8')) as unknown; }
|
|
275
|
+
catch (error) {
|
|
276
|
+
return { ok: false, error: { code: (error as NodeJS.ErrnoException).code === 'ENOENT' ? 'MANUAL_VALIDATION_TRANSACTION_MISSING' : 'MANUAL_VALIDATION_TRANSACTION_INVALID', message: error instanceof Error ? error.message : String(error) } };
|
|
277
|
+
}
|
|
278
|
+
return validateManualValidationTransaction(value, expected);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export {
|
|
282
|
+
MANUAL_VALIDATION_TRANSACTION_SCHEMA,
|
|
283
|
+
MANUAL_VALIDATION_TRANSACTION_VERSION,
|
|
284
|
+
archiveManualValidationGeneration,
|
|
285
|
+
createManualValidationTransaction,
|
|
286
|
+
manualValidationTransactionPath,
|
|
287
|
+
validateManualValidationGenerationArchive,
|
|
288
|
+
readManualValidationTransaction,
|
|
289
|
+
retryManualValidationTransaction,
|
|
290
|
+
summaryPreimageDigest,
|
|
291
|
+
transitionManualValidationTransaction,
|
|
292
|
+
validateManualValidationTransaction,
|
|
293
|
+
writeManualValidationTransactionAtomic
|
|
294
|
+
};
|
|
295
|
+
export type {
|
|
296
|
+
ManualValidationSummaryPreimage,
|
|
297
|
+
ManualValidationTransaction,
|
|
298
|
+
ManualValidationTransactionError,
|
|
299
|
+
ManualValidationTransactionErrorCode,
|
|
300
|
+
ManualValidationTransactionInput,
|
|
301
|
+
ManualValidationTransactionPatch,
|
|
302
|
+
ManualValidationTransactionPhase,
|
|
303
|
+
ManualValidationTransactionResult
|
|
304
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
type SourceLine = {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type VisibleHeading = SourceLine & {
|
|
8
|
+
level: number;
|
|
9
|
+
text: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type VisibleAnchor = SourceLine & {
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type VisibleMarkdown = {
|
|
17
|
+
hasUnclosedFence: boolean;
|
|
18
|
+
lines: SourceLine[];
|
|
19
|
+
headings: VisibleHeading[];
|
|
20
|
+
anchors: VisibleAnchor[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)\s*$/;
|
|
24
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
25
|
+
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})\s*$/;
|
|
26
|
+
const ANCHOR = /^\s*<a\s+id=(['"])([^'"]+)\1\s*><\/a>\s*$/;
|
|
27
|
+
|
|
28
|
+
function sourceLines(content: string): SourceLine[] {
|
|
29
|
+
const lines: SourceLine[] = [];
|
|
30
|
+
let start = 0;
|
|
31
|
+
while (start < content.length) {
|
|
32
|
+
const newline = content.indexOf('\n', start);
|
|
33
|
+
const end = newline === -1 ? content.length : newline;
|
|
34
|
+
lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
|
|
35
|
+
start = newline === -1 ? content.length : newline + 1;
|
|
36
|
+
}
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function scanVisibleMarkdown(content: string): VisibleMarkdown {
|
|
41
|
+
const lines = sourceLines(content);
|
|
42
|
+
const visible: SourceLine[] = [];
|
|
43
|
+
const headings: VisibleHeading[] = [];
|
|
44
|
+
const anchors: VisibleAnchor[] = [];
|
|
45
|
+
let fence: { character: '`' | '~'; length: number } | null = null;
|
|
46
|
+
|
|
47
|
+
for (const line of lines) {
|
|
48
|
+
if (fence) {
|
|
49
|
+
const close = line.text.match(FENCE_CLOSE);
|
|
50
|
+
if (close && close[1]![0] === fence.character && close[1]!.length >= fence.length) fence = null;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const open = line.text.match(FENCE_OPEN);
|
|
54
|
+
const marker = open?.[1];
|
|
55
|
+
const info = open?.[2] ?? '';
|
|
56
|
+
if (marker && !(marker[0] === '`' && info.includes('`'))) {
|
|
57
|
+
fence = { character: marker[0] as '`' | '~', length: marker.length };
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
visible.push(line);
|
|
61
|
+
const heading = line.text.match(HEADING);
|
|
62
|
+
if (heading) headings.push({ ...line, level: heading[1]!.length, text: heading[2]!.trim() });
|
|
63
|
+
const anchor = line.text.match(ANCHOR);
|
|
64
|
+
if (anchor) anchors.push({ ...line, id: anchor[2]! });
|
|
65
|
+
}
|
|
66
|
+
return { lines: visible, headings, anchors, hasUnclosedFence: fence !== null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { scanVisibleMarkdown };
|
|
70
|
+
export type { SourceLine, VisibleHeading, VisibleMarkdown };
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
|
+
import { artifactName, maxArtifactRound } from './artifact-name.ts';
|
|
5
6
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
6
7
|
import { validateCurrentTaskContract } from './current-contract.ts';
|
|
7
8
|
import { parseReviewSummary } from './review-artifacts.ts';
|
|
@@ -10,6 +11,7 @@ import type { LedgerDocument } from './ledger.ts';
|
|
|
10
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
11
12
|
import {
|
|
12
13
|
activateDelegation,
|
|
14
|
+
abortActivatedDelegation,
|
|
13
15
|
abortPreparedDelegation,
|
|
14
16
|
completeDelegationStage,
|
|
15
17
|
consumeDelegation,
|
|
@@ -39,6 +41,7 @@ import type {
|
|
|
39
41
|
OrchestrationModelPolicy,
|
|
40
42
|
OrchestrationRolePolicy
|
|
41
43
|
} from '../agent-clients/types.ts';
|
|
44
|
+
import type { LifecycleRecoveryAttestationV1 } from './control-authority.ts';
|
|
42
45
|
import {
|
|
43
46
|
captureRepositorySnapshot,
|
|
44
47
|
captureWorkspaceSnapshot,
|
|
@@ -57,6 +60,7 @@ import { hasOpenLifecycleExecution } from './activity-log.ts';
|
|
|
57
60
|
import { invalidationBlocks } from './invalidation.ts';
|
|
58
61
|
import type { InvalidationDocument } from './invalidation.ts';
|
|
59
62
|
import type { LifecycleAction, LifecycleFacts } from './capabilities.ts';
|
|
63
|
+
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
60
64
|
|
|
61
65
|
type OrchestrationStatus = 'running' | 'paused' | 'completed';
|
|
62
66
|
type ModelPolicySource = Readonly<{
|
|
@@ -173,6 +177,7 @@ type OrchestrationOptions = {
|
|
|
173
177
|
supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
|
|
174
178
|
validateLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
|
|
175
179
|
consumeLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
|
|
180
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
176
181
|
token?: () => string;
|
|
177
182
|
monotonicNow?: () => number;
|
|
178
183
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
@@ -275,7 +280,7 @@ function isRecovery(value: unknown): value is OrchestrationRecovery {
|
|
|
275
280
|
&& value.resultingStatus === 'running';
|
|
276
281
|
}
|
|
277
282
|
|
|
278
|
-
function isCompletionEvidence(value: unknown): value is CleanCompletionEvidence {
|
|
283
|
+
export function isCompletionEvidence(value: unknown): value is CleanCompletionEvidence {
|
|
279
284
|
return hasExactKeys(value, [
|
|
280
285
|
'kind', 'observedAt', 'head', 'headTree', 'worktreeTree', 'lastReviewedCommit', 'prNumber', 'prHead'
|
|
281
286
|
])
|
|
@@ -604,25 +609,13 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
604
609
|
return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
|
|
605
610
|
}
|
|
606
611
|
|
|
607
|
-
function highestRound(taskDir: string, family: string): number {
|
|
608
|
-
const pattern = new RegExp(`^${family}(?:-r(\\d+))?\\.md$`);
|
|
609
|
-
return fs.readdirSync(taskDir).reduce((max, name) => {
|
|
610
|
-
const match = pattern.exec(name);
|
|
611
|
-
return match ? Math.max(max, match[1] ? Number(match[1]) : 1) : max;
|
|
612
|
-
}, 0);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function artifactName(family: string, round: number): string {
|
|
616
|
-
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
612
|
function validateSavedCurrentDiffBase(
|
|
620
613
|
repoRoot: string,
|
|
621
614
|
metadata: ReturnType<typeof parseTypedTaskFrontmatter>,
|
|
622
615
|
taskDir: string,
|
|
623
616
|
reviewedHead: string
|
|
624
617
|
): Readonly<{ code: string; message: string }> | null {
|
|
625
|
-
const reviewRound =
|
|
618
|
+
const reviewRound = maxArtifactRound(fs.readdirSync(taskDir), 'review-code');
|
|
626
619
|
const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
|
|
627
620
|
const savedTargetHead = extractReviewTargetHead(reviewContent);
|
|
628
621
|
const savedDiffBase = extractReviewDiffBase(reviewContent);
|
|
@@ -660,10 +653,9 @@ function routeFromFacts(facts: LifecycleFacts): Omit<OrchestrationNext, 'request
|
|
|
660
653
|
const action = recommendation.action;
|
|
661
654
|
if (action === 'manual-validation' || action === 'validation-run') return null;
|
|
662
655
|
const family = action === 'analysis' ? 'analysis' : action;
|
|
663
|
-
const round =
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
})) + 1;
|
|
656
|
+
const round = maxArtifactRound([
|
|
657
|
+
...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])
|
|
658
|
+
], family) + 1;
|
|
667
659
|
const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
|
|
668
660
|
const role = action.startsWith('review-') ? 'reviewer' : 'executor';
|
|
669
661
|
return { action: command, role, stage: action, round, artifact: artifactName(family, round) };
|
|
@@ -719,7 +711,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
719
711
|
const routed = routeFromFacts(facts.facts);
|
|
720
712
|
if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
721
713
|
if ('completion' in routed) {
|
|
722
|
-
const reviewRound =
|
|
714
|
+
const reviewRound = maxArtifactRound(fs.readdirSync(resolved.taskDir), 'review-code');
|
|
723
715
|
const review = parseReviewSummary(fs.readFileSync(
|
|
724
716
|
path.join(resolved.taskDir, artifactName('review-code', reviewRound)),
|
|
725
717
|
'utf8'
|
|
@@ -830,6 +822,7 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
830
822
|
requestedModel?: string;
|
|
831
823
|
requestedReasoningEffort?: string;
|
|
832
824
|
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
825
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
833
826
|
}>,
|
|
834
827
|
options: OrchestrationOptions = {}
|
|
835
828
|
): OrchestrationResult {
|
|
@@ -866,6 +859,13 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
866
859
|
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
867
860
|
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
868
861
|
}
|
|
862
|
+
if (input.lifecycleRecoveryAttestation
|
|
863
|
+
&& (input.lifecycleRecoveryAttestation.phase !== 'orchestration.prepare'
|
|
864
|
+
|| input.lifecycleRecoveryAttestation.taskId !== resolved.taskId
|
|
865
|
+
|| input.lifecycleRecoveryAttestation.artifact !== next.artifact
|
|
866
|
+
|| input.lifecycleRecoveryAttestation.round !== next.round)) {
|
|
867
|
+
return failed('LIFECYCLE_AUTHORITY_TUPLE_MISMATCH', 'orchestration authority does not match the routed stage', resolved.taskId);
|
|
868
|
+
}
|
|
869
869
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
870
870
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
871
871
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -930,6 +930,7 @@ function prepareOrchestrationDelegation(
|
|
|
930
930
|
requestedModel?: string;
|
|
931
931
|
requestedReasoningEffort?: string;
|
|
932
932
|
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
933
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
933
934
|
}>,
|
|
934
935
|
options: OrchestrationOptions = {}
|
|
935
936
|
): OrchestrationResult {
|
|
@@ -1445,6 +1446,68 @@ function recoverPreparedOrchestrationDelegation(
|
|
|
1445
1446
|
}
|
|
1446
1447
|
}
|
|
1447
1448
|
|
|
1449
|
+
function recoverActivatedOrchestrationDelegationUnderLock(
|
|
1450
|
+
taskRef: string,
|
|
1451
|
+
event: Readonly<{
|
|
1452
|
+
receiptId: string;
|
|
1453
|
+
stage: DelegationStage;
|
|
1454
|
+
round: number;
|
|
1455
|
+
artifact: string;
|
|
1456
|
+
startedAgent: string;
|
|
1457
|
+
childId: string;
|
|
1458
|
+
stopRevision: number;
|
|
1459
|
+
consumer: string;
|
|
1460
|
+
consumedAt: string;
|
|
1461
|
+
}>,
|
|
1462
|
+
options: OrchestrationOptions = {}
|
|
1463
|
+
): OrchestrationResult {
|
|
1464
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1465
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1466
|
+
const run = readRun(resolved.taskDir, options);
|
|
1467
|
+
if (!run) return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
1468
|
+
const matches = [
|
|
1469
|
+
...(run.pendingDelegation ? [run.pendingDelegation] : []),
|
|
1470
|
+
...run.receipts.filter((receipt) => receipt.id === event.receiptId)
|
|
1471
|
+
];
|
|
1472
|
+
const receipt = matches.find((candidate) => candidate.id === event.receiptId);
|
|
1473
|
+
if (!receipt) return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
|
|
1474
|
+
if (
|
|
1475
|
+
receipt.taskId !== resolved.taskId
|
|
1476
|
+
|| receipt.stage !== event.stage
|
|
1477
|
+
|| receipt.round !== event.round
|
|
1478
|
+
|| receipt.artifact !== event.artifact
|
|
1479
|
+
|| receipt.childId !== event.childId
|
|
1480
|
+
|| normalizeAgentToken(event.startedAgent) !== normalizeAgentToken(receipt.client)
|
|
1481
|
+
) {
|
|
1482
|
+
return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
|
|
1483
|
+
}
|
|
1484
|
+
if (receipt.status === 'aborted' && run.pendingDelegation === null) {
|
|
1485
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1486
|
+
}
|
|
1487
|
+
if (run.pendingDelegation?.id !== receipt.id || receipt.status !== 'activated') {
|
|
1488
|
+
return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'recovery requires one matching activated pending delegation', resolved.taskId);
|
|
1489
|
+
}
|
|
1490
|
+
if (run.receipts.some((candidate) => candidate.id === receipt.id)) {
|
|
1491
|
+
return failed('ORCHESTRATION_RECEIPT_DUPLICATE', 'recovery receipt already exists in the completed receipt history', resolved.taskId);
|
|
1492
|
+
}
|
|
1493
|
+
const aborted = abortActivatedDelegation(receipt, {
|
|
1494
|
+
childId: event.childId,
|
|
1495
|
+
stopRevision: event.stopRevision,
|
|
1496
|
+
consumer: event.consumer,
|
|
1497
|
+
consumedAt: event.consumedAt
|
|
1498
|
+
});
|
|
1499
|
+
if (!aborted.ok) return failed(aborted.code, aborted.message, resolved.taskId);
|
|
1500
|
+
const updated = withUpdatedRun(run, {
|
|
1501
|
+
status: 'running',
|
|
1502
|
+
pause: null,
|
|
1503
|
+
nextStage: null,
|
|
1504
|
+
pendingDelegation: null,
|
|
1505
|
+
receipts: Object.freeze([...run.receipts, aborted.receipt])
|
|
1506
|
+
}, options.now);
|
|
1507
|
+
saveRun(resolved.taskDir, updated);
|
|
1508
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1448
1511
|
function completeOrchestrationStage(
|
|
1449
1512
|
taskRef: string,
|
|
1450
1513
|
event: Parameters<typeof completeDelegationStage>[1],
|
|
@@ -1599,6 +1662,7 @@ export {
|
|
|
1599
1662
|
readRun,
|
|
1600
1663
|
reconcileMatchingOrchestrationDelegation,
|
|
1601
1664
|
recoverPreparedOrchestrationDelegation,
|
|
1665
|
+
recoverActivatedOrchestrationDelegationUnderLock,
|
|
1602
1666
|
routeOrchestration,
|
|
1603
1667
|
sealMatchingOrchestrationDelegation,
|
|
1604
1668
|
sealMatchingOrchestrationDelegationWithHostEvidence,
|
|
@@ -1606,6 +1670,7 @@ export {
|
|
|
1606
1670
|
statusOrchestration,
|
|
1607
1671
|
};
|
|
1608
1672
|
export type {
|
|
1673
|
+
CleanCompletionEvidence,
|
|
1609
1674
|
OrchestrationCompletionPlanResult,
|
|
1610
1675
|
OrchestrationModelPolicy,
|
|
1611
1676
|
OrchestrationNext,
|