feat(reporting-governance): wire profile artifacts into contract and orchestrator
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import path from 'node:path';
|
||||
import { ensureSuccess, parseJsonStdout, runNodeScript } from './_script-runner.mjs';
|
||||
import { createRuntimeBinding, resolveScriptPath } from './runtime-binding.mjs';
|
||||
import { loadDeploymentProfileArtifact, createDeploymentBindingContract } from '../storage/profile-artifact.mjs';
|
||||
|
||||
export function runOrchestratorAdapter({
|
||||
scriptPath = null,
|
||||
runtimeBinding = null,
|
||||
profileArtifact = null,
|
||||
profileArtifactPath = null,
|
||||
profileId = null,
|
||||
repoRootOverride = null,
|
||||
state,
|
||||
evidenceDir,
|
||||
eventDir,
|
||||
@@ -23,8 +28,18 @@ export function runOrchestratorAdapter({
|
||||
claim = false,
|
||||
dryRun = false,
|
||||
} = {}) {
|
||||
const binding = runtimeBinding ?? createRuntimeBinding();
|
||||
const resolvedScriptPath = path.resolve(scriptPath ?? resolveScriptPath('orchestrator', { runtimeBinding: binding }));
|
||||
const deploymentBinding = profileArtifact || profileArtifactPath || profileId
|
||||
? createDeploymentBindingContract({
|
||||
artifact: profileArtifact ?? loadDeploymentProfileArtifact({ artifactPath: profileArtifactPath, profileId }).artifact,
|
||||
repoRootOverride,
|
||||
})
|
||||
: null;
|
||||
|
||||
const binding = runtimeBinding ?? createRuntimeBinding({
|
||||
cwd: repoRootOverride,
|
||||
scripts: deploymentBinding?.scripts,
|
||||
});
|
||||
const resolvedScriptPath = path.resolve(scriptPath ?? deploymentBinding?.entrypoint ?? resolveScriptPath('orchestrator', { runtimeBinding: binding }));
|
||||
const resolvedWatchdogScript = path.resolve(watchdogScript ?? resolveScriptPath('watchdog', { runtimeBinding: binding }));
|
||||
const resolvedDispatcherScript = path.resolve(dispatcherScript ?? resolveScriptPath('dispatcher', { runtimeBinding: binding }));
|
||||
const resolvedSupervisorScript = path.resolve(supervisorScript ?? resolveScriptPath('bridgeSupervisor', { runtimeBinding: binding }));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { evaluatePolicies } from './policy-evaluator.mjs';
|
||||
import { runCompatibilityPreflight } from './compatibility-preflight.mjs';
|
||||
import { planDecisionExecution } from './decision-runner.mjs';
|
||||
import { createDeploymentBindingContract } from '../storage/profile-artifact.mjs';
|
||||
|
||||
function createBlockedReceipt({ evaluation, preflight }) {
|
||||
return {
|
||||
@@ -57,6 +58,7 @@ export function executeGovernanceContract({
|
||||
context = {},
|
||||
profile = {},
|
||||
packageVersion,
|
||||
repoRootOverride,
|
||||
} = {}) {
|
||||
const evaluation = evaluatePolicies({
|
||||
event,
|
||||
@@ -81,10 +83,15 @@ export function executeGovernanceContract({
|
||||
capabilityDescriptor,
|
||||
});
|
||||
|
||||
const deploymentBinding = profile?.spec?.bindings
|
||||
? createDeploymentBindingContract({ artifact: profile, repoRootOverride })
|
||||
: null;
|
||||
|
||||
return {
|
||||
evaluation,
|
||||
preflight,
|
||||
planning,
|
||||
deploymentBinding,
|
||||
contract: {
|
||||
runtime: capabilityDescriptor?.metadata?.id ?? capabilityDescriptor?.runtime?.name ?? 'unknown-runtime',
|
||||
policy_id: evaluation.decision.policy_id,
|
||||
|
||||
@@ -29,11 +29,11 @@ scripts/long_task_watchdog.mjs
|
||||
|
||||
## Package mapping
|
||||
|
||||
- `src/adapters/watchdog-adapter.mjs` → watchdog trigger + canonical event seeding
|
||||
- `src/adapters/dispatcher-adapter.mjs` → queue to spool handoff
|
||||
- `src/adapters/bridge-adapter.mjs` → spool consumption + receipt writeback
|
||||
- `src/adapters/sender-binding-adapter.mjs` → sender contract boundary
|
||||
- `src/adapters/orchestrator-adapter.mjs` → deterministic composition entrypoint
|
||||
- `src/adapters/watchdog.mjs` → watchdog trigger + canonical event seeding
|
||||
- `src/adapters/dispatcher.mjs` → queue to spool handoff
|
||||
- `src/adapters/bridge-supervisor.mjs` → spool consumption + receipt writeback
|
||||
- `src/adapters/sender-binding.mjs` → sender contract boundary
|
||||
- `src/adapters/orchestrator.mjs` → deterministic composition entrypoint
|
||||
|
||||
## Runtime artifact classes
|
||||
|
||||
|
||||
@@ -3,11 +3,58 @@ import path from 'node:path';
|
||||
|
||||
const packageRoot = path.resolve(import.meta.dirname, '..', '..');
|
||||
const repoRoot = path.resolve(packageRoot, '..', '..');
|
||||
const EXPECTED_KIND = 'DeploymentProfileArtifact';
|
||||
const EXPECTED_API_VERSION = 'reporting-governance/v1alpha1';
|
||||
|
||||
function readJsonFile(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function assertNonEmptyString(value, label) {
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
throw new Error(`${label} must be a non-empty string`);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function assertObjectRecord(value, label) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error(`${label} must be an object record`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function validateDeploymentProfileArtifact(artifact) {
|
||||
if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact)) {
|
||||
throw new Error('deployment profile artifact must be an object');
|
||||
}
|
||||
if (artifact.kind !== EXPECTED_KIND) {
|
||||
throw new Error(`deployment profile artifact kind must be ${EXPECTED_KIND}`);
|
||||
}
|
||||
if (artifact.apiVersion !== EXPECTED_API_VERSION) {
|
||||
throw new Error(`deployment profile artifact apiVersion must be ${EXPECTED_API_VERSION}`);
|
||||
}
|
||||
|
||||
const bindings = artifact?.spec?.bindings;
|
||||
if (!bindings || typeof bindings !== 'object' || Array.isArray(bindings)) {
|
||||
throw new Error('deployment profile artifact bindings are required');
|
||||
}
|
||||
|
||||
assertNonEmptyString(bindings.entrypoint, 'deployment profile artifact spec.bindings.entrypoint');
|
||||
const scripts = assertObjectRecord(bindings.scripts, 'deployment profile artifact spec.bindings.scripts');
|
||||
const artifactRoots = assertObjectRecord(bindings.artifact_roots, 'deployment profile artifact spec.bindings.artifact_roots');
|
||||
assertNonEmptyString(artifact?.spec?.package?.pluginVersion, 'deployment profile artifact spec.package.pluginVersion');
|
||||
|
||||
for (const [key, relativePath] of Object.entries(scripts)) {
|
||||
assertNonEmptyString(relativePath, `deployment profile artifact spec.bindings.scripts.${key}`);
|
||||
}
|
||||
for (const [key, relativePath] of Object.entries(artifactRoots)) {
|
||||
assertNonEmptyString(relativePath, `deployment profile artifact spec.bindings.artifact_roots.${key}`);
|
||||
}
|
||||
|
||||
return artifact;
|
||||
}
|
||||
|
||||
export function resolvePackageArtifactPath(...segments) {
|
||||
return path.resolve(packageRoot, ...segments);
|
||||
}
|
||||
@@ -17,7 +64,7 @@ export function loadDeploymentProfileArtifact({ artifactPath, profileId } = {})
|
||||
artifactPath
|
||||
?? resolvePackageArtifactPath('profiles', `${profileId ?? 'strict-manager-mode'}.profile.json`)
|
||||
);
|
||||
const artifact = readJsonFile(resolvedPath);
|
||||
const artifact = validateDeploymentProfileArtifact(readJsonFile(resolvedPath));
|
||||
return {
|
||||
artifactPath: resolvedPath,
|
||||
artifact,
|
||||
@@ -25,30 +72,30 @@ export function loadDeploymentProfileArtifact({ artifactPath, profileId } = {})
|
||||
}
|
||||
|
||||
export function createDeploymentBindingContract({ artifact, repoRootOverride } = {}) {
|
||||
if (!artifact?.spec?.bindings) {
|
||||
throw new Error('deployment profile artifact bindings are required');
|
||||
}
|
||||
|
||||
const validatedArtifact = validateDeploymentProfileArtifact(artifact);
|
||||
const root = path.resolve(repoRootOverride ?? repoRoot);
|
||||
const scripts = Object.fromEntries(
|
||||
Object.entries(artifact.spec.bindings.scripts ?? {}).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
|
||||
Object.entries(validatedArtifact.spec.bindings.scripts).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
|
||||
);
|
||||
const artifactRoots = Object.fromEntries(
|
||||
Object.entries(artifact.spec.bindings.artifact_roots ?? {}).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
|
||||
Object.entries(validatedArtifact.spec.bindings.artifact_roots).map(([key, relativePath]) => [key, path.resolve(root, relativePath)])
|
||||
);
|
||||
|
||||
return {
|
||||
runtime: artifact.spec.bindings.runtime ?? artifact.metadata?.runtime ?? 'unknown-runtime',
|
||||
entrypoint: path.resolve(root, artifact.spec.bindings.entrypoint),
|
||||
pluginVersion: artifact.spec?.package?.pluginVersion ?? null,
|
||||
compatibilityMode: artifact.metadata?.compatibility_mode ?? 'strict_envelope',
|
||||
runtime: validatedArtifact.spec.bindings.runtime ?? validatedArtifact.metadata?.runtime ?? 'unknown-runtime',
|
||||
entrypoint: path.resolve(root, validatedArtifact.spec.bindings.entrypoint),
|
||||
pluginVersion: validatedArtifact.spec.package.pluginVersion,
|
||||
compatibilityMode: validatedArtifact.metadata?.compatibility_mode ?? 'strict_envelope',
|
||||
scripts,
|
||||
artifactRoots,
|
||||
};
|
||||
}
|
||||
|
||||
export const __testables = {
|
||||
EXPECTED_KIND,
|
||||
EXPECTED_API_VERSION,
|
||||
packageRoot,
|
||||
repoRoot,
|
||||
readJsonFile,
|
||||
validateDeploymentProfileArtifact,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user