chati-dev 4.5.16 → 4.5.27
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 +8 -3
- package/bin/chati.js +124 -66
- package/framework/agents/build/dev.md +5 -5
- package/framework/agents/deploy/devops.md +7 -7
- package/framework/agents/discover/brief.md +4 -4
- package/framework/agents/discover/brownfield-wu.md +3 -3
- package/framework/agents/discover/greenfield-wu.md +3 -3
- package/framework/agents/plan/architect.md +2 -2
- package/framework/agents/plan/detail.md +4 -4
- package/framework/agents/plan/phases.md +2 -2
- package/framework/agents/plan/tasks.md +2 -2
- package/framework/agents/plan/ux.md +2 -2
- package/framework/agents/quality/qa-implementation.md +11 -10
- package/framework/agents/quality/qa-planning.md +3 -3
- package/framework/agents/quality/qa-visual.md +1 -1
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +18 -18
- package/framework/context/protocols.md +1 -1
- package/framework/context/quality.md +1 -1
- package/framework/context/root.md +4 -4
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/domains/agents/orchestrator.yaml +2 -2
- package/framework/domains/constitution.yaml +1 -1
- package/framework/hooks/advance-trigger.js +4 -6
- package/framework/hooks/git-push-authority.js +45 -37
- package/framework/hooks/mode-governance.js +149 -40
- package/framework/hooks/model-governance.js +13 -20
- package/framework/hooks/prism-engine.js +74 -92
- package/framework/hooks/reasoning-escalator.js +23 -40
- package/framework/hooks/session-digest.js +12 -13
- package/framework/hooks/session-reader.js +224 -0
- package/framework/hooks/session-writer.js +195 -0
- package/framework/hooks/team-quality-gate.js +34 -24
- package/framework/i18n/en.yaml +2 -2
- package/framework/i18n/es.yaml +2 -2
- package/framework/i18n/fr.yaml +2 -2
- package/framework/i18n/pt.yaml +2 -2
- package/framework/intelligence/context-engine.md +4 -4
- package/framework/intelligence/memory-layer.md +1 -1
- package/framework/manifest.json +129 -119
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati-router.js +252 -25
- package/framework/orchestrator/chati.md +238 -70
- package/framework/schemas/session.schema.json +1 -1
- package/framework/tasks/orchestrator-deviation.md +1 -1
- package/framework/tasks/orchestrator-escalate.md +1 -1
- package/framework/tasks/orchestrator-handoff.md +6 -6
- package/framework/tasks/orchestrator-health.md +5 -9
- package/framework/tasks/orchestrator-mode-switch.md +3 -7
- package/framework/tasks/orchestrator-resume.md +10 -14
- package/framework/tasks/orchestrator-route.md +3 -3
- package/framework/tasks/orchestrator-spawn-terminal.md +1 -1
- package/framework/tasks/orchestrator-status.md +9 -9
- package/framework/tasks/orchestrator-suggest-mode.md +1 -1
- package/framework/tasks/qa-impl-consolidate.md +2 -2
- package/framework/tasks/qa-impl-performance-test.md +4 -4
- package/framework/tasks/qa-impl-regression-check.md +4 -4
- package/framework/tasks/qa-impl-sast-scan.md +1 -1
- package/framework/tasks/qa-impl-test-execute.md +1 -1
- package/framework/tasks/qa-impl-verdict.md +2 -2
- package/framework/tasks/qa-planning-consolidate.md +3 -3
- package/framework/tasks/qa-planning-coverage-plan.md +2 -2
- package/framework/tasks/qa-planning-gate-define.md +4 -4
- package/framework/tasks/qa-planning-risk-matrix.md +4 -4
- package/framework/tasks/qa-planning-test-strategy.md +2 -2
- package/node_modules/@chati/browser-capability/src/index.js +12 -2
- package/node_modules/@chati/planning/src/index.js +24 -4
- package/node_modules/@chati/provider-registry/src/index.js +11 -0
- package/node_modules/@chati/rail/src/index.js +1967 -83
- package/node_modules/@chati/release-lane/README.md +12 -8
- package/node_modules/@chati/release-lane/package.json +1 -1
- package/node_modules/@chati/release-lane/src/index.js +1426 -58
- package/node_modules/@chati/review-council/src/index.js +63 -0
- package/node_modules/@chati/tracking-clickup/README.md +13 -0
- package/node_modules/@chati/tracking-clickup/src/index.js +690 -30
- package/package-artifact-manifest.json +1 -0
- package/package-artifact-manifest.sig +1 -0
- package/package.json +16 -7
- package/scripts/verify-real-harness-e2e.js +1581 -0
- package/src/config/framework-adapter.js +4 -4
- package/src/installer/core.js +148 -53
- package/src/installer/manifest.js +140 -9
- package/src/installer/package-artifact.js +249 -0
- package/src/installer/templates.js +65 -20
- package/src/installer-v2/catalog-client.js +531 -23
- package/src/installer-v2/index.js +91 -34
- package/src/installer-v2/installation-authority.js +327 -0
- package/src/installer-v2/provider-executable.js +235 -0
- package/src/installer-v2/wizard-installation.js +1 -1
- package/src/orchestrator/browser-runtime.js +44 -13
- package/src/orchestrator/cli.js +1545 -174
- package/src/orchestrator/clickup-projection.js +43 -1
- package/src/orchestrator/clickup-runtime.js +355 -39
- package/src/orchestrator/doctor.js +16 -3
- package/src/orchestrator/index.js +16 -0
- package/src/orchestrator/planning-runtime.js +20 -2
- package/src/orchestrator/rail-adjudication-evidence.js +234 -0
- package/src/orchestrator/rail-evidence-authority.js +147 -0
- package/src/orchestrator/rail-execution-evidence.js +45 -0
- package/src/orchestrator/rail-runtime.js +836 -56
- package/src/orchestrator/release-runtime.js +65 -6
- package/src/orchestrator/review-runtime.js +186 -41
- package/src/orchestrator/runtime-installation-v2.js +236 -20
- package/src/orchestrator/session-manager.js +1167 -53
- package/src/terminal/adapters/claude-adapter.js +3 -1
- package/src/terminal/adapters/codex-adapter.js +2 -0
- package/src/terminal/adapters/grok-adapter.js +7 -4
- package/src/terminal/handoff-parser.js +19 -1
- package/src/terminal/prompt-builder.js +9 -1
- package/src/terminal/provider-preflight.js +36 -3
- package/src/terminal/rail-execution-worktree.js +213 -0
- package/src/terminal/rail-prompts.js +169 -0
- package/src/terminal/rail-readonly-workspace.js +324 -0
- package/src/terminal/run-agent.js +384 -24
- package/src/terminal/run-parallel.js +5 -0
- package/src/terminal/run-rail-adjudication.js +323 -0
- package/src/terminal/run-rail-review.js +291 -0
- package/src/terminal/run-rail-rework.js +325 -0
- package/src/terminal/run-rail-task.js +380 -0
- package/src/terminal/run-team.js +5 -0
- package/src/terminal/spawner.js +1225 -77
- package/src/wizard/index.js +3 -2
|
@@ -1,14 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
ReleaseLane, ReleaseStateAuthority, ReleaseTransport,
|
|
4
|
+
SignedHumanAuthorizationVerifier, SignedSmokeEvidenceVerifier,
|
|
5
|
+
} from '@chati/release-lane';
|
|
6
|
+
import { loadRailHandoff, resolveRailCompletionReference } from './rail-runtime.js';
|
|
7
|
+
import { loadRuntimeInstallationV2 } from './runtime-installation-v2.js';
|
|
8
|
+
import {
|
|
9
|
+
signHostAuthorityPayload, verifyHostAuthorityPayload,
|
|
10
|
+
} from '../installer-v2/installation-authority.js';
|
|
11
|
+
|
|
12
|
+
const RELEASE_STATE_AUTHORITY_DOMAIN = 'chati-release-state-v1';
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
signedHumanAuthorizationPayload,
|
|
16
|
+
signedSmokeEvidencePayload,
|
|
17
|
+
} from '@chati/release-lane';
|
|
3
18
|
|
|
4
19
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
20
|
+
* Project release authority. Completion evidence is resolved from the local
|
|
21
|
+
* RAIL journal. External I/O remains behind an explicitly injected minted
|
|
22
|
+
* transport and cannot run before scoped human authorization and smoke proof.
|
|
8
23
|
*/
|
|
9
|
-
export function createProjectReleaseLane({
|
|
24
|
+
export function createProjectReleaseLane({
|
|
25
|
+
projectDir, handoff_id, authorization_issuer_id, authorization_public_key,
|
|
26
|
+
smoke_issuer_id, smoke_public_key,
|
|
27
|
+
clock = () => new Date(),
|
|
28
|
+
} = {}) {
|
|
29
|
+
if (typeof handoff_id !== 'string' || !/^[A-Za-z0-9._-]+$/.test(handoff_id)) {
|
|
30
|
+
throw new Error('handoff_id must be safe for the project-local release journal');
|
|
31
|
+
}
|
|
32
|
+
const artifact = loadRuntimeInstallationV2(projectDir, { clock });
|
|
33
|
+
if (!artifact) throw new Error('release lane requires an authorized v2 installation');
|
|
34
|
+
const { handoff } = loadRailHandoff({ projectDir, handoff_id, clock });
|
|
35
|
+
if (handoff.project_id !== artifact.project_id) {
|
|
36
|
+
throw new Error('release lane handoff does not belong to the authorized installed project');
|
|
37
|
+
}
|
|
10
38
|
return new ReleaseLane({
|
|
11
39
|
clock,
|
|
12
40
|
verify_completion: (immutable_ref) => resolveRailCompletionReference({ projectDir, handoff_id, immutable_ref, clock }),
|
|
41
|
+
authorization_verifier: new SignedHumanAuthorizationVerifier({
|
|
42
|
+
issuer_id: authorization_issuer_id,
|
|
43
|
+
public_key: authorization_public_key,
|
|
44
|
+
}),
|
|
45
|
+
smoke_verifier: new SignedSmokeEvidenceVerifier({
|
|
46
|
+
issuer_id: smoke_issuer_id,
|
|
47
|
+
public_key: smoke_public_key,
|
|
48
|
+
}),
|
|
49
|
+
state_authority: new ReleaseStateAuthority({
|
|
50
|
+
sign: (material) => {
|
|
51
|
+
const signed = signHostAuthorityPayload(material, { domain: RELEASE_STATE_AUTHORITY_DOMAIN });
|
|
52
|
+
return { authority_id: signed.authority_id, authority_signature: signed.signature };
|
|
53
|
+
},
|
|
54
|
+
verify: (material, receipt) => verifyHostAuthorityPayload(material, {
|
|
55
|
+
domain: RELEASE_STATE_AUTHORITY_DOMAIN,
|
|
56
|
+
authorityId: receipt?.authority_id,
|
|
57
|
+
signature: receipt?.authority_signature,
|
|
58
|
+
}),
|
|
59
|
+
}),
|
|
60
|
+
require_authenticated_state: true,
|
|
61
|
+
authority_context: {
|
|
62
|
+
project_id: artifact.project_id,
|
|
63
|
+
handoff_id,
|
|
64
|
+
},
|
|
65
|
+
require_authority_context: true,
|
|
66
|
+
require_production_transport: true,
|
|
67
|
+
state_path: join(projectDir, '.chati', 'release-lane', `${handoff_id}.jsonl`),
|
|
13
68
|
});
|
|
14
69
|
}
|
|
70
|
+
|
|
71
|
+
export function createProjectReleaseTransport({ transport_id, authority_id, public_key, release, rollback, lookup } = {}) {
|
|
72
|
+
return new ReleaseTransport({ transport_id, authority_id, public_key, release, rollback, lookup });
|
|
73
|
+
}
|
|
@@ -1,24 +1,96 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
3
4
|
import { acceptReview, decideReviewProgress } from '@chati/review-council';
|
|
5
|
+
import { sha256 } from '@chati/core';
|
|
4
6
|
import { loadRuntimeInstallationV2 } from './runtime-installation-v2.js';
|
|
5
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createProjectRailEngine, loadLatestRailExecutionEvidence, loadRailHandoff, resolveRailAttemptContext,
|
|
9
|
+
} from './rail-runtime.js';
|
|
10
|
+
import { verifyTerminalAuthorityReceipt } from '../terminal/spawner.js';
|
|
11
|
+
import { buildRailReviewPrompt } from '../terminal/rail-prompts.js';
|
|
12
|
+
import { assertRailReadOnlyCommitWorkspaceUnchanged } from '../terminal/rail-readonly-workspace.js';
|
|
6
13
|
|
|
7
14
|
function fail(code, message) { const error = new Error(message); error.code = code; throw error; }
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
|
|
16
|
+
function trustedGitEnvironment(source = process.env) {
|
|
17
|
+
const environment = Object.fromEntries(Object.entries(source).filter(([key]) => !key.startsWith('GIT_')));
|
|
18
|
+
return {
|
|
19
|
+
...environment,
|
|
20
|
+
GIT_CONFIG_NOSYSTEM: '1',
|
|
21
|
+
GIT_CONFIG_GLOBAL: process.platform === 'win32' ? 'NUL' : '/dev/null',
|
|
22
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseAuthorizedReviewPayload(text) {
|
|
27
|
+
if (typeof text !== 'string') fail('RAIL_REVIEW_OUTPUT_INVALID', 'review output must be a string');
|
|
28
|
+
const startToken = '<CHATI_RAIL_REVIEW>';
|
|
29
|
+
const endToken = '</CHATI_RAIL_REVIEW>';
|
|
30
|
+
const starts = text.split(startToken).length - 1;
|
|
31
|
+
const ends = text.split(endToken).length - 1;
|
|
32
|
+
if (starts !== 1 || ends !== 1) fail('RAIL_REVIEW_OUTPUT_INVALID', 'reviewer must return exactly one CHATI_RAIL_REVIEW envelope');
|
|
33
|
+
const start = text.indexOf(startToken);
|
|
34
|
+
const end = text.indexOf(endToken, start + startToken.length);
|
|
35
|
+
try { return JSON.parse(text.slice(start + startToken.length, end).trim()); }
|
|
36
|
+
catch (error) { return fail('RAIL_REVIEW_OUTPUT_INVALID', `reviewer returned invalid JSON: ${error.message}`); }
|
|
11
37
|
}
|
|
12
|
-
function reviewDir(projectDir, attemptId) { return join(projectDir, '.chati/v2/rail/reviews', safeId(attemptId, 'attempt_id')); }
|
|
13
|
-
function reviewPath(projectDir, attemptId, reviewId) { return join(reviewDir(projectDir, attemptId), `${safeId(reviewId, 'review_id')}.json`); }
|
|
14
38
|
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
39
|
+
export function recordRailReviewDispatch({
|
|
40
|
+
projectDir, attempt_id, invocation, launch_receipt, prompt, reviewWorkspace, clock,
|
|
41
|
+
} = {}) {
|
|
42
|
+
const lineage = resolveAttemptLineage(projectDir, attempt_id, clock);
|
|
43
|
+
const context = resolveRailAttemptContext({ projectDir, handoff_id: lineage.handoff_id, attempt_id, clock });
|
|
44
|
+
const engine = createProjectRailEngine({ projectDir, ...(clock === undefined ? {} : { clock }) });
|
|
45
|
+
const reviewSubject = resolveReviewGitSubject({ projectDir, attempt_id, clock });
|
|
46
|
+
if (!reviewWorkspace
|
|
47
|
+
|| reviewWorkspace.canonical_project_dir !== realpathSync(resolve(projectDir))
|
|
48
|
+
|| reviewWorkspace.base_commit_ref !== reviewSubject.base_commit_ref
|
|
49
|
+
|| reviewWorkspace.result_commit_ref !== reviewSubject.result_commit_ref) {
|
|
50
|
+
fail('RAIL_REVIEW_WORKSPACE_MISMATCH', 'review dispatch requires an immutable workspace for the exact result commit');
|
|
51
|
+
}
|
|
52
|
+
const workspaceState = assertRailReadOnlyCommitWorkspaceUnchanged({
|
|
53
|
+
workspace: reviewWorkspace,
|
|
54
|
+
label: 'RAIL review dispatch',
|
|
55
|
+
});
|
|
56
|
+
const canonicalSubject = {
|
|
57
|
+
...reviewSubject,
|
|
58
|
+
workspace_digest: sha256(workspaceState),
|
|
59
|
+
};
|
|
60
|
+
const round = engine.reviewEvidenceForAttempt(attempt_id).length + 1;
|
|
61
|
+
const canonicalPrompt = buildRailReviewPrompt({
|
|
62
|
+
projectDir: reviewWorkspace.subject_dir,
|
|
63
|
+
context,
|
|
64
|
+
invocation,
|
|
65
|
+
reviewSubject: canonicalSubject,
|
|
66
|
+
round,
|
|
21
67
|
});
|
|
68
|
+
if (prompt !== canonicalPrompt || invocation?.prompt_digest !== sha256(canonicalPrompt)) {
|
|
69
|
+
fail('NONCANONICAL_REVIEW_PROMPT', 'review dispatch must use the exact canonical prompt for this attempt and round');
|
|
70
|
+
}
|
|
71
|
+
if (!verifyTerminalAuthorityReceipt(launch_receipt, {
|
|
72
|
+
purpose: 'terminal_launch', authority_kind: 'rail_review',
|
|
73
|
+
invocation_id: invocation?.invocation_id, launch_nonce: invocation?.launch_nonce,
|
|
74
|
+
provider_id: invocation?.provider_id, harness_id: invocation?.harness_id,
|
|
75
|
+
model_id: invocation?.model_pin?.model_id,
|
|
76
|
+
reasoning_configuration: invocation?.model_pin?.reasoning_configuration,
|
|
77
|
+
task_id: context.task.task_id,
|
|
78
|
+
working_dir: resolve(reviewWorkspace.subject_dir),
|
|
79
|
+
prompt_digest: invocation.prompt_digest,
|
|
80
|
+
})) fail('UNTRUSTED_REVIEW_LAUNCH', 'review dispatch requires a terminal-authority launch receipt');
|
|
81
|
+
const artifact = loadRuntimeInstallationV2(projectDir, { ...(clock === undefined ? {} : { clock }) });
|
|
82
|
+
if (!artifact) fail('V2_INSTALLATION_REQUIRED', 'RAIL review dispatch requires a v2 installation');
|
|
83
|
+
return engine.recordReviewDispatch({
|
|
84
|
+
attempt_id, invocation, launch_receipt,
|
|
85
|
+
installation: artifact.installation, snapshot: artifact.capability_snapshot,
|
|
86
|
+
project_dir: projectDir,
|
|
87
|
+
working_dir: reviewWorkspace.subject_dir,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function listRailReviewEvidence({ projectDir, attempt_id, clock } = {}) {
|
|
92
|
+
return createProjectRailEngine({ projectDir, ...(clock === undefined ? {} : { clock }) })
|
|
93
|
+
.reviewEvidenceForAttempt(attempt_id);
|
|
22
94
|
}
|
|
23
95
|
|
|
24
96
|
function resolveAttemptLineage(projectDir, attemptId, clock) {
|
|
@@ -32,43 +104,116 @@ function resolveAttemptLineage(projectDir, attemptId, clock) {
|
|
|
32
104
|
handoff_id: handoff.handoff_id,
|
|
33
105
|
handoff_manifest_digest: handoff.seal.manifest_digest,
|
|
34
106
|
task_id: attempt.task_id,
|
|
107
|
+
base_commit_ref: attempt.git_commit_ref,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resolveReviewGitSubject({ projectDir, attempt_id, clock } = {}) {
|
|
112
|
+
const lineage = resolveAttemptLineage(projectDir, attempt_id, clock);
|
|
113
|
+
const evidence = loadLatestRailExecutionEvidence({ projectDir, attempt_id, clock });
|
|
114
|
+
const engine = createProjectRailEngine({ projectDir, ...(clock === undefined ? {} : { clock }) });
|
|
115
|
+
const latestControl = engine.records.filter((record) => ['review_decided', 'adjudication_decided'].includes(record.type)
|
|
116
|
+
&& record.payload?.attempt_id === attempt_id).at(-1);
|
|
117
|
+
const requiredReworkSource = latestControl?.type === 'review_decided'
|
|
118
|
+
&& latestControl.payload.decision?.decision === 'normal-rework'
|
|
119
|
+
? latestControl.payload.review_ref
|
|
120
|
+
: latestControl?.type === 'adjudication_decided'
|
|
121
|
+
&& latestControl.payload.decision?.outcome === 'bounded-correction'
|
|
122
|
+
? latestControl.payload.adjudication_ref
|
|
123
|
+
: null;
|
|
124
|
+
if (requiredReworkSource && evidence.source_decision_ref !== requiredReworkSource) {
|
|
125
|
+
fail('RAIL_REWORK_RESULT_REQUIRED', 'latest canonical rework decision has no successful execution result');
|
|
126
|
+
}
|
|
127
|
+
if (evidence.attempt_id !== attempt_id || evidence.handoff_id !== lineage.handoff_id || evidence.task_id !== lineage.task_id) {
|
|
128
|
+
fail('REVIEW_EXECUTION_EVIDENCE_MISMATCH', 'review execution evidence is not bound to the claimed handoff, task and attempt');
|
|
129
|
+
}
|
|
130
|
+
const result_commit_ref = evidence.result_commit_ref;
|
|
131
|
+
if (typeof result_commit_ref !== 'string' || !/^[a-f0-9]{40,64}$/.test(result_commit_ref)) {
|
|
132
|
+
fail('REVIEW_RESULT_COMMIT_REQUIRED', 'review requires a result commit captured by immutable execution evidence');
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
execFileSync('git', ['-C', projectDir, 'cat-file', '-e', `${result_commit_ref}^{commit}`], {
|
|
136
|
+
stdio: 'ignore', env: trustedGitEnvironment(),
|
|
137
|
+
});
|
|
138
|
+
} catch {
|
|
139
|
+
return fail('REVIEW_GIT_SUBJECT_UNAVAILABLE', 'the execution evidence result commit is unavailable in local Git');
|
|
140
|
+
}
|
|
141
|
+
if (result_commit_ref === lineage.base_commit_ref) {
|
|
142
|
+
fail('REVIEW_RESULT_COMMIT_REQUIRED', 'review requires a result commit distinct from the sealed base commit');
|
|
143
|
+
}
|
|
144
|
+
const ancestry = spawnSync('git', ['-C', projectDir, 'merge-base', '--is-ancestor', lineage.base_commit_ref, result_commit_ref], {
|
|
145
|
+
env: trustedGitEnvironment(),
|
|
35
146
|
});
|
|
147
|
+
if (ancestry.status !== 0) fail('REVIEW_RESULT_NOT_DESCENDANT', 'review result commit must descend from the sealed base commit');
|
|
148
|
+
return Object.freeze({ ...lineage, result_commit_ref, execution_evidence_ref: evidence.evidence_id });
|
|
36
149
|
}
|
|
37
150
|
|
|
38
|
-
/**
|
|
39
|
-
export function recordRailReview({
|
|
151
|
+
/** Appends selected-only review evidence to the canonical RAIL journal. */
|
|
152
|
+
export function recordRailReview({
|
|
153
|
+
projectDir, review, review_output, terminal_authority_receipt,
|
|
154
|
+
invocation, result_commit_ref, clock = () => new Date(),
|
|
155
|
+
} = {}) {
|
|
156
|
+
const payload = parseAuthorizedReviewPayload(review_output);
|
|
157
|
+
const outputReview = {
|
|
158
|
+
findings: payload.findings ?? [], verdict: payload.verdict,
|
|
159
|
+
impasse: payload.impasse ?? { kind: 'none' },
|
|
160
|
+
...(payload.authority_reason ? { authority_reason: payload.authority_reason } : {}),
|
|
161
|
+
...(payload.authority_reason_ref ? { authority_reason_ref: payload.authority_reason_ref } : {}),
|
|
162
|
+
};
|
|
163
|
+
const suppliedReview = {
|
|
164
|
+
findings: review?.findings ?? [], verdict: review?.verdict,
|
|
165
|
+
impasse: review?.impasse ?? { kind: 'none' },
|
|
166
|
+
...(review?.authority_reason ? { authority_reason: review.authority_reason } : {}),
|
|
167
|
+
...(review?.authority_reason_ref ? { authority_reason_ref: review.authority_reason_ref } : {}),
|
|
168
|
+
};
|
|
169
|
+
if (sha256(outputReview) !== sha256(suppliedReview)) {
|
|
170
|
+
fail('RAIL_REVIEW_PAYLOAD_MISMATCH', 'review material is not the payload returned by the authorized reviewer process');
|
|
171
|
+
}
|
|
172
|
+
if (!verifyTerminalAuthorityReceipt(terminal_authority_receipt, {
|
|
173
|
+
purpose: 'terminal_completion', authority_kind: 'rail_review',
|
|
174
|
+
invocation_id: invocation?.invocation_id, launch_nonce: invocation?.launch_nonce,
|
|
175
|
+
provider_id: invocation?.provider_id, harness_id: invocation?.harness_id,
|
|
176
|
+
model_id: invocation?.model_pin?.model_id,
|
|
177
|
+
reasoning_configuration: invocation?.model_pin?.reasoning_configuration,
|
|
178
|
+
exit_status: 0,
|
|
179
|
+
}) || terminal_authority_receipt.stdout_digest !== sha256(review_output)) {
|
|
180
|
+
fail('UNTRUSTED_REVIEW_EVIDENCE', 'review requires output bound to a successful terminal authority receipt');
|
|
181
|
+
}
|
|
40
182
|
const artifact = loadRuntimeInstallationV2(projectDir, { clock });
|
|
41
183
|
if (!artifact) fail('V2_INSTALLATION_REQUIRED', 'RAIL review requires a v2 installation');
|
|
42
184
|
const validated = acceptReview({ review, invocation, installation: artifact.installation, snapshot: artifact.capability_snapshot, clock });
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
185
|
+
const baseLineage = resolveAttemptLineage(projectDir, validated.attempt_id, clock);
|
|
186
|
+
const engine = createProjectRailEngine({ projectDir, clock });
|
|
187
|
+
const review_ref = `rail-review://${baseLineage.handoff_id}/${validated.attempt_id}/${validated.review_id}`;
|
|
188
|
+
const existingJournal = engine.records.find((record) => record.type === 'review_decided' && record.payload?.review_ref === review_ref);
|
|
189
|
+
if (existingJournal) {
|
|
190
|
+
if (result_commit_ref !== undefined && result_commit_ref !== existingJournal.payload.result_commit_ref) {
|
|
191
|
+
fail('REVIEW_GIT_SUBJECT_CHANGED', 'Git subject differs from the immutable canonical review');
|
|
192
|
+
}
|
|
193
|
+
const lineage = Object.freeze({ ...baseLineage, result_commit_ref: existingJournal.payload.result_commit_ref });
|
|
194
|
+
const journal = engine.recordReviewDecision({
|
|
195
|
+
attempt_id: validated.attempt_id, review_ref, decision: existingJournal.payload.decision, review: validated, invocation,
|
|
196
|
+
terminal_authority_receipt,
|
|
197
|
+
installation: artifact.installation, snapshot: artifact.capability_snapshot,
|
|
198
|
+
git_dir: projectDir, result_commit_ref: existingJournal.payload.result_commit_ref,
|
|
199
|
+
});
|
|
200
|
+
return Object.freeze({ review_ref, lineage, review: validated, decision: existingJournal.payload.decision, journal, written: false });
|
|
201
|
+
}
|
|
202
|
+
const resolvedSubject = resolveReviewGitSubject({ projectDir, attempt_id: validated.attempt_id, clock });
|
|
203
|
+
if (result_commit_ref !== undefined && result_commit_ref !== resolvedSubject.result_commit_ref) {
|
|
204
|
+
fail('REVIEW_GIT_SUBJECT_CHANGED', 'Git HEAD changed after the review subject was captured');
|
|
205
|
+
}
|
|
206
|
+
const lineage = resolvedSubject;
|
|
207
|
+
const prior = engine.reviewEvidenceForAttempt(validated.attempt_id);
|
|
50
208
|
const decision = decideReviewProgress({
|
|
51
209
|
prior_review_evidence: prior, current_review: validated, reviewer_invocation: invocation,
|
|
52
210
|
installation: artifact.installation, snapshot: artifact.capability_snapshot, clock,
|
|
53
211
|
});
|
|
54
|
-
const
|
|
55
|
-
let written = false;
|
|
56
|
-
if (!matching) {
|
|
57
|
-
mkdirSync(reviewDir(projectDir, validated.attempt_id), { recursive: true, mode: 0o700 });
|
|
58
|
-
const temporary = `${path}.${process.pid}.tmp`;
|
|
59
|
-
writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
60
|
-
try {
|
|
61
|
-
linkSync(temporary, path);
|
|
62
|
-
written = true;
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (error?.code !== 'EEXIST' || readFileSync(path, 'utf8') !== content) fail('REVIEW_STORAGE_CONFLICT', `review ${validated.review_id} raced with different material`);
|
|
65
|
-
} finally {
|
|
66
|
-
if (existsSync(temporary)) unlinkSync(temporary);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const journal = createProjectRailEngine({ projectDir, clock }).recordReviewDecision({
|
|
212
|
+
const journal = engine.recordReviewDecision({
|
|
70
213
|
attempt_id: validated.attempt_id, review_ref, decision, review: validated, invocation,
|
|
71
|
-
|
|
214
|
+
terminal_authority_receipt,
|
|
215
|
+
installation: artifact.installation, snapshot: artifact.capability_snapshot,
|
|
216
|
+
git_dir: projectDir, result_commit_ref: lineage.result_commit_ref,
|
|
72
217
|
});
|
|
73
|
-
return Object.freeze({
|
|
218
|
+
return Object.freeze({ review_ref, lineage, review: validated, decision, journal, written: !journal.replay });
|
|
74
219
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { doctorV2 } from '../installer-v2/index.js';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
4
|
import { assertEligibleInvocation } from '@chati/provider-registry';
|
|
5
|
+
import { selectCanonicalReviewer } from '@chati/review-council';
|
|
6
|
+
import {
|
|
7
|
+
readInstallationArtifactFile, verifyInstallationCatalogProvenance,
|
|
8
|
+
} from '../installer-v2/catalog-client.js';
|
|
9
|
+
import { verifyInstallationArtifactAuthority } from '../installer-v2/installation-authority.js';
|
|
5
10
|
|
|
6
11
|
const AGENT_ACTIONS = Object.freeze({
|
|
7
12
|
'greenfield-wu': 'discovery', 'brownfield-wu': 'discovery', brief: 'discovery',
|
|
8
13
|
detail: 'planning', architect: 'planning', ux: 'planning', phases: 'planning', tasks: 'planning',
|
|
9
|
-
'qa-planning': 'review',
|
|
10
|
-
devops: 'build',
|
|
14
|
+
'qa-planning': 'review',
|
|
11
15
|
});
|
|
12
16
|
|
|
13
17
|
export const V2_PLANNING_AGENTS = Object.freeze([
|
|
@@ -15,7 +19,78 @@ export const V2_PLANNING_AGENTS = Object.freeze([
|
|
|
15
19
|
'detail', 'architect', 'ux', 'qa-planning', 'phases', 'tasks',
|
|
16
20
|
]);
|
|
17
21
|
|
|
22
|
+
const V2_PLANNING_PREDECESSORS = Object.freeze({
|
|
23
|
+
brief: Object.freeze(['work-understanding']),
|
|
24
|
+
detail: Object.freeze(['work-understanding', 'brief']),
|
|
25
|
+
architect: Object.freeze(['work-understanding', 'brief']),
|
|
26
|
+
ux: Object.freeze(['work-understanding', 'brief']),
|
|
27
|
+
'qa-planning': Object.freeze(['work-understanding', 'brief', 'detail', 'architect', 'ux']),
|
|
28
|
+
phases: Object.freeze(['work-understanding', 'brief', 'detail', 'architect', 'ux', 'qa-planning']),
|
|
29
|
+
tasks: Object.freeze(['work-understanding', 'brief', 'detail', 'architect', 'ux', 'qa-planning', 'phases']),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export function assertV2PlanningPredecessors(session, agent) {
|
|
33
|
+
const projectType = session?.project_type || session?.project?.type || 'greenfield';
|
|
34
|
+
const required = (V2_PLANNING_PREDECESSORS[agent] || []).map((name) =>
|
|
35
|
+
name === 'work-understanding' ? (projectType === 'brownfield' ? 'brownfield-wu' : 'greenfield-wu') : name
|
|
36
|
+
);
|
|
37
|
+
const incomplete = required.filter((name) =>
|
|
38
|
+
session?.agents?.[name]?.status !== 'completed' || !session?.completed_agents?.includes(name)
|
|
39
|
+
);
|
|
40
|
+
if (incomplete.length > 0) {
|
|
41
|
+
const error = new Error(`Agent ${agent} cannot advance before its canonical predecessors complete: ${incomplete.join(', ')}`);
|
|
42
|
+
error.code = 'V2_PLANNING_PREDECESSORS_INCOMPLETE';
|
|
43
|
+
error.incomplete = incomplete;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze({ valid: true, required: Object.freeze(required) });
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
const RAIL_ACTIONS = new Set(['build', 'review']);
|
|
50
|
+
const REASONING_CONFIGURATIONS = Object.freeze(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
51
|
+
|
|
52
|
+
function highestCompatibleReasoning(binding, model) {
|
|
53
|
+
const maximum = model.highest_reasoning_configuration || 'xhigh';
|
|
54
|
+
const maximumRank = REASONING_CONFIGURATIONS.indexOf(maximum);
|
|
55
|
+
if (maximumRank < 0) return null;
|
|
56
|
+
const allowed = new Set(binding.allowed_reasoning_configurations || []);
|
|
57
|
+
for (let rank = maximumRank; rank >= 0; rank -= 1) {
|
|
58
|
+
const candidate = REASONING_CONFIGURATIONS[rank];
|
|
59
|
+
if (allowed.has(candidate)) return candidate;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function nearestCompatibleReasoning(binding, model, preferred) {
|
|
65
|
+
const maximum = model.highest_reasoning_configuration || 'xhigh';
|
|
66
|
+
const maximumRank = REASONING_CONFIGURATIONS.indexOf(maximum);
|
|
67
|
+
const preferredRank = REASONING_CONFIGURATIONS.indexOf(preferred);
|
|
68
|
+
if (maximumRank < 0 || preferredRank < 0) return null;
|
|
69
|
+
const allowed = (binding.allowed_reasoning_configurations || [])
|
|
70
|
+
.filter((candidate) => {
|
|
71
|
+
const rank = REASONING_CONFIGURATIONS.indexOf(candidate);
|
|
72
|
+
return rank >= 0 && rank <= maximumRank;
|
|
73
|
+
});
|
|
74
|
+
return allowed.sort((left, right) => {
|
|
75
|
+
const leftRank = REASONING_CONFIGURATIONS.indexOf(left);
|
|
76
|
+
const rightRank = REASONING_CONFIGURATIONS.indexOf(right);
|
|
77
|
+
return Math.abs(leftRank - preferredRank) - Math.abs(rightRank - preferredRank)
|
|
78
|
+
|| leftRank - rightRank;
|
|
79
|
+
})[0] || null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function compatibleReasoning(binding, model, requested, preferred) {
|
|
83
|
+
if (requested === undefined || requested === null) {
|
|
84
|
+
return nearestCompatibleReasoning(binding, model, preferred);
|
|
85
|
+
}
|
|
86
|
+
const requestedRank = REASONING_CONFIGURATIONS.indexOf(requested);
|
|
87
|
+
const maximumRank = REASONING_CONFIGURATIONS.indexOf(model.highest_reasoning_configuration || 'xhigh');
|
|
88
|
+
if (requestedRank < 0 || maximumRank < 0 || requestedRank > maximumRank
|
|
89
|
+
|| !binding.allowed_reasoning_configurations?.includes(requested)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return requested;
|
|
93
|
+
}
|
|
19
94
|
|
|
20
95
|
export function describeRuntimeInstallationV2(artifact) {
|
|
21
96
|
const railExecutionPool = artifact.installation.enabled_providers.map((binding) => {
|
|
@@ -48,21 +123,54 @@ export function describeRuntimeInstallationV2(artifact) {
|
|
|
48
123
|
});
|
|
49
124
|
}
|
|
50
125
|
|
|
51
|
-
export function loadRuntimeInstallationV2(projectDir, {
|
|
52
|
-
|
|
53
|
-
|
|
126
|
+
export function loadRuntimeInstallationV2(projectDir, {
|
|
127
|
+
clock = () => new Date(), authorityDirectory, source = null,
|
|
128
|
+
} = {}) {
|
|
129
|
+
const installationSource = source ?? readInstallationArtifactFile(projectDir);
|
|
130
|
+
if (!installationSource) {
|
|
131
|
+
if (existsSync(join(resolve(projectDir), '.chati', 'v2'))) {
|
|
132
|
+
throw Object.assign(new Error('V2 installation artifact is required for a V2 project'), {
|
|
133
|
+
code: 'INSTALLATION_ARTIFACT_REQUIRED',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
54
138
|
let artifact;
|
|
55
|
-
try { artifact = JSON.parse(
|
|
139
|
+
try { artifact = JSON.parse(installationSource.content); } catch (error) {
|
|
56
140
|
throw Object.assign(new Error('Invalid v2 installation artifact'), { code: 'INVALID_INSTALLATION_ARTIFACT', cause: error });
|
|
57
141
|
}
|
|
58
142
|
const doctor = doctorV2(artifact, { clock });
|
|
59
143
|
if (!doctor.passed) throw Object.assign(new Error('V2 installation artifact failed doctor'), { code: doctor.checks[0]?.code || 'INVALID_INSTALLATION_ARTIFACT' });
|
|
144
|
+
verifyInstallationArtifactAuthority(projectDir, artifact, { authorityDirectory });
|
|
145
|
+
verifyInstallationCatalogProvenance(projectDir, artifact.capability_snapshot, { clock });
|
|
60
146
|
return Object.freeze(artifact);
|
|
61
147
|
}
|
|
62
148
|
|
|
63
|
-
export function
|
|
64
|
-
const
|
|
65
|
-
|
|
149
|
+
export function resolveRuntimeExecutableBindingV2({ artifact, provider_id, harness_id } = {}) {
|
|
150
|
+
const binding = artifact?.installation?.enabled_providers?.find((candidate) =>
|
|
151
|
+
candidate.provider_id === provider_id && candidate.harness_id === harness_id);
|
|
152
|
+
if (!binding) {
|
|
153
|
+
throw Object.assign(new Error('provider executable binding is not part of the selected installation'), { code: 'BINDING_NOT_SELECTED' });
|
|
154
|
+
}
|
|
155
|
+
if (typeof binding.executable_path !== 'string' || !binding.executable_path.startsWith('/')
|
|
156
|
+
|| !/^[a-f0-9]{64}$/.test(binding.executable_sha256 || '')) {
|
|
157
|
+
throw Object.assign(
|
|
158
|
+
new Error(`installed ${provider_id}/${harness_id} binding does not seal a provider executable`),
|
|
159
|
+
{ code: 'PROVIDER_EXECUTABLE_BINDING_REQUIRED' },
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
executable_path: binding.executable_path,
|
|
164
|
+
executable_sha256: binding.executable_sha256,
|
|
165
|
+
supporting_executables: Object.freeze((binding.supporting_executables || []).map((entry) => Object.freeze({
|
|
166
|
+
executable_path: entry.executable_path,
|
|
167
|
+
executable_sha256: entry.executable_sha256,
|
|
168
|
+
}))),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveRuntimeActionV2({ artifact, action, routingKey, binding, model_id, reasoning_configuration, clock = () => new Date() }) {
|
|
173
|
+
let hasEligibleModel = false;
|
|
66
174
|
const candidates = artifact.installation.enabled_providers.flatMap((candidateBinding) => {
|
|
67
175
|
if (binding && (candidateBinding.provider_id !== binding.provider_id || candidateBinding.harness_id !== binding.harness_id)) return [];
|
|
68
176
|
return candidateBinding.allowed_models.flatMap((candidateModelId) => {
|
|
@@ -71,10 +179,21 @@ export function resolveRuntimeInvocationV2({ artifact, agent, binding, model_id,
|
|
|
71
179
|
candidate.provider_id === candidateBinding.provider_id && candidate.model_id === candidateModelId
|
|
72
180
|
);
|
|
73
181
|
if (!catalogModel?.actions.includes(action)) return [];
|
|
182
|
+
hasEligibleModel = true;
|
|
183
|
+
const defaultReasoning = catalogModel.tier === 'worker'
|
|
184
|
+
? 'low'
|
|
185
|
+
: catalogModel.tier === 'workhorse'
|
|
186
|
+
? 'medium'
|
|
187
|
+
: 'high';
|
|
188
|
+
const reasoning = compatibleReasoning(
|
|
189
|
+
candidateBinding, catalogModel, reasoning_configuration, defaultReasoning,
|
|
190
|
+
);
|
|
191
|
+
if (!reasoning) return [];
|
|
74
192
|
return [{
|
|
75
193
|
binding: candidateBinding,
|
|
76
194
|
model: catalogModel,
|
|
77
|
-
|
|
195
|
+
reasoning,
|
|
196
|
+
priority: catalogModel.routing_priority?.[routingKey] ?? catalogModel.routing_priority?.[action] ?? 0,
|
|
78
197
|
}];
|
|
79
198
|
});
|
|
80
199
|
}).sort((left, right) =>
|
|
@@ -83,22 +202,119 @@ export function resolveRuntimeInvocationV2({ artifact, agent, binding, model_id,
|
|
|
83
202
|
|| `${left.binding.harness_id}:${left.model.model_id}`.localeCompare(`${right.binding.harness_id}:${right.model.model_id}`)
|
|
84
203
|
);
|
|
85
204
|
const selectedCandidate = candidates[0];
|
|
86
|
-
if (!selectedCandidate)
|
|
205
|
+
if (!selectedCandidate) {
|
|
206
|
+
throw Object.assign(
|
|
207
|
+
new Error(`No eligible installed model and reasoning configuration for ${routingKey}/${action}`),
|
|
208
|
+
{ code: hasEligibleModel ? 'NO_ELIGIBLE_REASONING' : 'NO_ELIGIBLE_MODEL' },
|
|
209
|
+
);
|
|
210
|
+
}
|
|
87
211
|
const selected = selectedCandidate.binding;
|
|
88
212
|
const model = selectedCandidate.model.model_id;
|
|
89
|
-
const defaultReasoning = selectedCandidate.model.tier === 'worker'
|
|
90
|
-
? 'low'
|
|
91
|
-
: selectedCandidate.model.tier === 'workhorse'
|
|
92
|
-
? 'medium'
|
|
93
|
-
: 'high';
|
|
94
213
|
const invocation = {
|
|
95
214
|
provider_id: selected.provider_id, harness_id: selected.harness_id, action,
|
|
96
215
|
model_pin: {
|
|
97
216
|
model_id: model,
|
|
98
217
|
catalog_snapshot_ref: artifact.capability_snapshot.snapshot_id,
|
|
99
|
-
reasoning_configuration:
|
|
218
|
+
reasoning_configuration: selectedCandidate.reasoning,
|
|
100
219
|
},
|
|
101
220
|
};
|
|
102
221
|
assertEligibleInvocation({ installation: artifact.installation, snapshot: artifact.capability_snapshot, invocation, clock });
|
|
103
222
|
return Object.freeze(invocation);
|
|
104
223
|
}
|
|
224
|
+
|
|
225
|
+
export function resolveRuntimeInvocationV2({ artifact, agent, binding, model_id, reasoning_configuration, clock = () => new Date() }) {
|
|
226
|
+
if (!V2_PLANNING_AGENTS.includes(agent)) {
|
|
227
|
+
throw Object.assign(
|
|
228
|
+
new Error(`Agent ${agent} is not executable through the v2 planning runtime; implementation and release must use RAIL`),
|
|
229
|
+
{ code: 'V2_RAIL_EXECUTION_REQUIRED' },
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const action = AGENT_ACTIONS[agent];
|
|
233
|
+
if (!action) throw Object.assign(new Error(`No v2 action mapping for agent ${agent}`), { code: 'UNMAPPED_AGENT_ACTION' });
|
|
234
|
+
return resolveRuntimeActionV2({ artifact, action, routingKey: agent, binding, model_id, reasoning_configuration, clock });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function resolveRuntimeRailInvocationV2({ artifact, action = 'build', binding, model_id, reasoning_configuration, clock = () => new Date() }) {
|
|
238
|
+
if (!RAIL_ACTIONS.has(action)) {
|
|
239
|
+
throw Object.assign(new Error(`Action ${action} is not executable through RAIL`), { code: 'INVALID_RAIL_ACTION' });
|
|
240
|
+
}
|
|
241
|
+
return resolveRuntimeActionV2({ artifact, action, routingKey: `rail:${action}`, binding, model_id, reasoning_configuration, clock });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Selects the highest-ranked installed reviewer at the strongest available independence level. */
|
|
245
|
+
export function resolveRuntimeRailReviewerV2({ artifact, builder_identity, clock = () => new Date() }) {
|
|
246
|
+
return selectCanonicalReviewer({
|
|
247
|
+
installation: artifact.installation,
|
|
248
|
+
snapshot: artifact.capability_snapshot,
|
|
249
|
+
builder_identity,
|
|
250
|
+
clock,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function resolveRuntimeAdjudicationV2({ artifact, clock = () => new Date() }) {
|
|
255
|
+
const candidates = artifact.installation.enabled_providers.flatMap((binding) =>
|
|
256
|
+
binding.allowed_models.flatMap((modelId) => {
|
|
257
|
+
const model = artifact.capability_snapshot.models.find((candidate) =>
|
|
258
|
+
candidate.provider_id === binding.provider_id && candidate.model_id === modelId
|
|
259
|
+
);
|
|
260
|
+
if (!model?.actions.includes('adjudication')) return [];
|
|
261
|
+
const reasoning = highestCompatibleReasoning(binding, model);
|
|
262
|
+
if (!reasoning) return [];
|
|
263
|
+
return [{ binding, model, reasoning }];
|
|
264
|
+
})
|
|
265
|
+
).sort((left, right) =>
|
|
266
|
+
(right.model.adjudication_priority ?? 0) - (left.model.adjudication_priority ?? 0)
|
|
267
|
+
|| (right.model.routing_priority?.adjudication ?? 0) - (left.model.routing_priority?.adjudication ?? 0)
|
|
268
|
+
|| `${left.binding.harness_id}:${left.model.model_id}`.localeCompare(`${right.binding.harness_id}:${right.model.model_id}`)
|
|
269
|
+
);
|
|
270
|
+
const selected = candidates[0];
|
|
271
|
+
if (!selected) throw Object.assign(new Error('No eligible installed model for adjudication'), { code: 'NO_ELIGIBLE_ADJUDICATOR' });
|
|
272
|
+
const invocation = {
|
|
273
|
+
provider_id: selected.binding.provider_id,
|
|
274
|
+
harness_id: selected.binding.harness_id,
|
|
275
|
+
action: 'adjudication',
|
|
276
|
+
model_pin: {
|
|
277
|
+
model_id: selected.model.model_id,
|
|
278
|
+
catalog_snapshot_ref: artifact.capability_snapshot.snapshot_id,
|
|
279
|
+
reasoning_configuration: selected.reasoning,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
assertEligibleInvocation({ installation: artifact.installation, snapshot: artifact.capability_snapshot, invocation, clock });
|
|
283
|
+
return Object.freeze(invocation);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Revalidate a terminal command against the exact route selected from the
|
|
288
|
+
* integrity-checked installation artifact derived from the signed capability
|
|
289
|
+
* catalog. Eligibility alone is insufficient because an
|
|
290
|
+
* installation can permit several models while the router selected one.
|
|
291
|
+
*/
|
|
292
|
+
export function assertRuntimeCommandV2({ artifact, agent, command = {}, reasoning_escalation = false, clock = () => new Date() }) {
|
|
293
|
+
const expected = reasoning_escalation
|
|
294
|
+
? resolveRuntimeAdjudicationV2({ artifact, clock })
|
|
295
|
+
: resolveRuntimeInvocationV2({ artifact, agent, clock });
|
|
296
|
+
const actual = {
|
|
297
|
+
provider_id: command.provider_id || null,
|
|
298
|
+
harness_id: command.harness_id || null,
|
|
299
|
+
model_id: command.model_id || null,
|
|
300
|
+
reasoning_configuration: command.reasoning_configuration || null,
|
|
301
|
+
catalog_snapshot_ref: command.catalog_snapshot_ref || null,
|
|
302
|
+
strict_provider: command.strict_provider === true,
|
|
303
|
+
};
|
|
304
|
+
const comparisons = [
|
|
305
|
+
['provider_id', expected.provider_id, actual.provider_id],
|
|
306
|
+
['harness_id', expected.harness_id, actual.harness_id],
|
|
307
|
+
['model_id', expected.model_pin.model_id, actual.model_id],
|
|
308
|
+
['reasoning_configuration', expected.model_pin.reasoning_configuration, actual.reasoning_configuration],
|
|
309
|
+
['catalog_snapshot_ref', expected.model_pin.catalog_snapshot_ref, actual.catalog_snapshot_ref],
|
|
310
|
+
['strict_provider', true, actual.strict_provider],
|
|
311
|
+
];
|
|
312
|
+
const mismatches = comparisons.filter(([, expectedValue, actualValue]) => expectedValue !== actualValue);
|
|
313
|
+
if (mismatches.length > 0) {
|
|
314
|
+
throw Object.assign(
|
|
315
|
+
new Error(`Runtime command does not match the integrity-checked v2 route: ${mismatches.map(([field]) => field).join(', ')}`),
|
|
316
|
+
{ code: 'RUNTIME_ROUTE_BINDING_MISMATCH', mismatches: mismatches.map(([field]) => field) },
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return expected;
|
|
320
|
+
}
|