@aifabrix/builder 2.51.0 → 2.51.1
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/jest.isolated-projects.js +77 -1
- package/jest.projects.js +5 -426
- package/lib/api/assistants.api.js +67 -0
- package/lib/api/datasources-extended.api.js +6 -6
- package/lib/api/evidence-authoring-helpers.js +267 -0
- package/lib/api/evidence-authoring.api.js +337 -0
- package/lib/api/evidence.api.js +57 -1
- package/lib/api/index.js +24 -7
- package/lib/api/pipeline.api.js +43 -3
- package/lib/api/records-bulk.api.js +28 -3
- package/lib/api/types/evidence.types.js +156 -8
- package/lib/api/work.api.js +283 -0
- package/lib/app/run-deploy-job.js +108 -0
- package/lib/app/run-helpers.js +10 -3
- package/lib/app/run.js +8 -0
- package/lib/channels/add-channel.js +54 -23
- package/lib/channels/approval-guides/chatgpt.js +8 -7
- package/lib/channels/approval-guides/microsoft-copilot.js +7 -8
- package/lib/channels/channel-artifact-generator.js +14 -2
- package/lib/channels/channel-credential.js +3 -1
- package/lib/channels/channel-probe-evidence.js +79 -0
- package/lib/channels/channel-probe.js +265 -13
- package/lib/channels/channel-runtime-resolver.js +120 -29
- package/lib/channels/channel-status-display.js +44 -0
- package/lib/channels/channel-targets.js +7 -0
- package/lib/channels/chatgpt-runtime-contract.js +29 -0
- package/lib/channels/microsoft-copilot-package.js +280 -0
- package/lib/channels/runtime-tool-catalog.js +165 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/cli/setup-external-system-help.js +8 -2
- package/lib/cli/setup-external-system.js +12 -0
- package/lib/commands/app-test.js +2 -14
- package/lib/commands/datasource-load-export-cli.js +6 -1
- package/lib/commands/deploy-gate-failure-details.js +19 -17
- package/lib/commands/dev-down.js +2 -6
- package/lib/commands/evidence-kind.js +65 -15
- package/lib/commands/governance-generate-external.js +56 -21
- package/lib/commands/lifecycle-external.js +10 -4
- package/lib/commands/repair-datasource-apply.js +10 -1
- package/lib/commands/repair-datasource-run.js +4 -2
- package/lib/commands/repair-datasource-test-payload.js +80 -19
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +42 -3
- package/lib/commands/repair-internal.js +9 -1
- package/lib/commands/repair-openapi-sync.js +34 -17
- package/lib/commands/role-assistant.js +313 -0
- package/lib/commands/upload-data-sync-finalize.js +5 -4
- package/lib/commands/upload-sidecar-followups.js +144 -47
- package/lib/commands/upload.js +43 -6
- package/lib/commands/verify-governance-combined-external.js +73 -9
- package/lib/commands/verify-governance-db.js +80 -25
- package/lib/commands/verify-governance-external.js +29 -5
- package/lib/commands/wizard-core-helpers.js +7 -3
- package/lib/commands/wizard-core.js +78 -9
- package/lib/datasource/bulk-loader-run-batches.js +4 -2
- package/lib/datasource/bulk-loader-service.js +5 -1
- package/lib/datasource/datasource-validate-summary.js +2 -2
- package/lib/datasource/validate.js +13 -6
- package/lib/external-system/deploy-helpers.js +35 -1
- package/lib/external-system/deploy.js +23 -10
- package/lib/external-system/download-helpers.js +111 -2
- package/lib/external-system/download-role-assistant.js +55 -0
- package/lib/external-system/download.js +45 -26
- package/lib/external-system/test-helpers.js +1 -1
- package/lib/external-system/test.js +39 -3
- package/lib/generator/builders.js +3 -0
- package/lib/generator/wizard.js +51 -4
- package/lib/lifecycle/access-control-abac.js +121 -0
- package/lib/lifecycle/lifecycle-governance-options.js +4 -1
- package/lib/lifecycle/product-model.js +3 -2
- package/lib/lifecycle/report-display.js +14 -2
- package/lib/programmatic/capability-registry.js +20 -1
- package/lib/programmatic/command-options.js +1 -0
- package/lib/programmatic/index.js +4 -0
- package/lib/programmatic/manifest-patch-section.js +48 -9
- package/lib/programmatic/manifest-section-registry-data.js +3 -2
- package/lib/programmatic/openapi-apply-result-schemas.js +44 -0
- package/lib/programmatic/openapi-component-schemas.js +16 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +55 -5
- package/lib/programmatic/openapi-evidence-schemas.js +161 -0
- package/lib/programmatic/openapi-manifest-schemas.js +6 -2
- package/lib/programmatic/openapi-operation-bindings.js +99 -0
- package/lib/programmatic/openapi-request-schemas.js +36 -1
- package/lib/programmatic/openapi-result-schemas.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-certify.js +5 -1
- package/lib/programmatic/openapi-schema-descriptions-core.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-evidence.js +156 -0
- package/lib/programmatic/openapi-schema-descriptions-manifest.js +4 -3
- package/lib/programmatic/openapi-schema-descriptions-operations.js +70 -0
- package/lib/programmatic/openapi-spec.js +47 -28
- package/lib/programmatic/orchestration-openapi-meta.js +5 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +89 -2
- package/lib/programmatic/run-certify.js +15 -2
- package/lib/programmatic/run-deploy.js +74 -0
- package/lib/programmatic/run-evidence-authoring.js +80 -0
- package/lib/programmatic/run-protection.js +124 -0
- package/lib/programmatic/run-template-workspace.js +28 -1
- package/lib/programmatic/workspace-context.js +6 -0
- package/lib/protection/auth-context.js +48 -6
- package/lib/protection/paths.js +22 -0
- package/lib/protection/run-protection-create-helpers.js +22 -1
- package/lib/protection/run-protection-create.js +1 -1
- package/lib/protection/sync-after-upload.js +1 -1
- package/lib/role-assistant/evidence-sync.js +450 -0
- package/lib/role-assistant/github-workflow.js +96 -0
- package/lib/role-assistant/knowledge-sync.js +465 -0
- package/lib/role-assistant/settings.js +126 -0
- package/lib/role-assistant/test-cases-evaluate.js +170 -0
- package/lib/role-assistant/test-cases-honesty.js +184 -0
- package/lib/role-assistant/test-cases.js +364 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +203 -0
- package/lib/role-assistant/test-runner-workhub.js +385 -0
- package/lib/role-assistant/test-runner.js +500 -0
- package/lib/schema/application-schema.json +16 -2
- package/lib/schema/deployment-rules.yaml +1 -0
- package/lib/schema/external-datasource.schema.json +38 -4
- package/lib/schema/external-system.schema.json +4 -4
- package/lib/schema/infra.parameter.yaml +20 -0
- package/lib/schema/role-assistant-settings.schema.json +108 -0
- package/lib/utils/api.js +52 -27
- package/lib/utils/app-path-resolve.js +72 -24
- package/lib/utils/authentication-shape.js +3 -1
- package/lib/utils/deployment-validation.js +28 -3
- package/lib/utils/docker-run-ephemeral.js +55 -0
- package/lib/utils/enterprise-knowledge-paths.js +29 -12
- package/lib/utils/env-copy-keycloak.js +196 -0
- package/lib/utils/env-copy.js +34 -1
- package/lib/utils/external-system-readiness-compact-display.js +3 -0
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +67 -23
- package/lib/utils/pipeline-upload-auth-retry.js +15 -3
- package/lib/utils/role-assistant-paths.js +98 -0
- package/lib/utils/schema-loader.js +41 -0
- package/lib/utils/schema-resolver.js +36 -4
- package/lib/utils/token-manager.js +38 -29
- package/lib/utils/url-declarative-resolve-load-doc.js +38 -1
- package/lib/validation/external-manifest-validator.js +31 -8
- package/lib/validation/validate-external-file.js +8 -1
- package/package.json +2 -1
- package/scripts/export-builder-api-evidence.js +1 -1
- package/templates/applications/dataplane/application.yaml +23 -15
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/dataplane/rbac.yaml +14 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/channels/chatgpt/install.template.md.hbs +42 -24
- package/templates/channels/chatgpt/integration/README.md +10 -7
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +3 -3
- package/templates/channels/chatgpt/integration/env.template +1 -1
- package/templates/channels/chatgpt/manifest.template.json.hbs +36 -5
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +28 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +22 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +32 -22
- package/templates/channels/microsoft-copilot/integration/README.md +9 -7
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +1 -1
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +7 -6
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -6
- /package/.cursor/plans/{157.1-security-quality-violations-by-type.csv → Done/157.1-security-quality-violations-by-type.csv} +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role assistant test-case expectation evaluation.
|
|
3
|
+
* @fileoverview Evaluate lifecycle/capability/fact/honesty expectations for a case result
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
buildHonestyObservation,
|
|
10
|
+
evaluateHonestyExpectations
|
|
11
|
+
} = require('./test-cases-honesty');
|
|
12
|
+
|
|
13
|
+
function getValueAtPath(obj, dottedPath) {
|
|
14
|
+
const parts = String(dottedPath || '').split('.').filter(Boolean);
|
|
15
|
+
let cursor = obj;
|
|
16
|
+
for (const key of parts) {
|
|
17
|
+
if (!cursor || typeof cursor !== 'object' || !(key in cursor)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
cursor = cursor[key];
|
|
21
|
+
}
|
|
22
|
+
return cursor;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function evaluateLifecycleStateExpectation(expectedState, observedState) {
|
|
26
|
+
if (!expectedState) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const expected = String(expectedState).trim();
|
|
30
|
+
const passed = expected === observedState;
|
|
31
|
+
return {
|
|
32
|
+
id: 'expected.lifecycleState',
|
|
33
|
+
passed,
|
|
34
|
+
message: passed
|
|
35
|
+
? `Lifecycle state matched expected value "${expected}".`
|
|
36
|
+
: `Lifecycle state expected "${expected}" but got "${observedState}".`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function evaluateCapabilityExpectations(requiredList, observedCapabilities) {
|
|
41
|
+
if (!Array.isArray(requiredList) || requiredList.length === 0) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const observedSet = new Set(observedCapabilities);
|
|
45
|
+
return requiredList.map((capability) => {
|
|
46
|
+
const key = String(capability || '').trim();
|
|
47
|
+
const passed = observedSet.has(key);
|
|
48
|
+
return {
|
|
49
|
+
id: `expected.capabilities.required:${key}`,
|
|
50
|
+
passed,
|
|
51
|
+
message: passed
|
|
52
|
+
? `Required capability "${key}" observed.`
|
|
53
|
+
: `Required capability "${key}" was not observed.`
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function evaluateFactExpectations(expectedFacts, observation) {
|
|
59
|
+
if (!expectedFacts || typeof expectedFacts !== 'object' || Array.isArray(expectedFacts)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
return Object.entries(expectedFacts).map(([factPath, expectedValue]) => {
|
|
63
|
+
const actualValue = getValueAtPath(observation, factPath);
|
|
64
|
+
const passed = JSON.stringify(actualValue) === JSON.stringify(expectedValue);
|
|
65
|
+
return {
|
|
66
|
+
id: `expected.facts:${factPath}`,
|
|
67
|
+
passed,
|
|
68
|
+
message: passed
|
|
69
|
+
? `Fact "${factPath}" matched expected value.`
|
|
70
|
+
: `Fact "${factPath}" expected ${JSON.stringify(expectedValue)} but got ${JSON.stringify(actualValue)}.`
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function evaluateProhibitedExpectations(prohibited, observation) {
|
|
76
|
+
if (!Array.isArray(prohibited) || prohibited.length === 0) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
return prohibited.map((factPath) => {
|
|
80
|
+
const actualValue = getValueAtPath(observation, factPath);
|
|
81
|
+
const passed = actualValue === undefined || actualValue === null || actualValue === false;
|
|
82
|
+
return {
|
|
83
|
+
id: `expected.prohibited:${factPath}`,
|
|
84
|
+
passed,
|
|
85
|
+
message: passed
|
|
86
|
+
? `Prohibited marker "${factPath}" was not observed.`
|
|
87
|
+
: `Prohibited marker "${factPath}" observed: ${JSON.stringify(actualValue)}.`
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildCaseObservation(result, lifecycleVerdict, portalUrl) {
|
|
93
|
+
const {
|
|
94
|
+
deriveLifecycleState,
|
|
95
|
+
collectObservedCapabilities
|
|
96
|
+
} = require('./test-cases');
|
|
97
|
+
const observedState = deriveLifecycleState(result, lifecycleVerdict);
|
|
98
|
+
const observedCapabilities = collectObservedCapabilities(result);
|
|
99
|
+
const workResult =
|
|
100
|
+
result?.workResult && typeof result.workResult === 'object' ? result.workResult : null;
|
|
101
|
+
return {
|
|
102
|
+
lifecycleState: observedState,
|
|
103
|
+
verdict: lifecycleVerdict,
|
|
104
|
+
openApiPublished: result?.openApiPublished === true,
|
|
105
|
+
portalUrl,
|
|
106
|
+
report: result?.report || {},
|
|
107
|
+
stepsRun: Array.isArray(result?.stepsRun) ? result.stepsRun : [],
|
|
108
|
+
capabilities: { observed: observedCapabilities },
|
|
109
|
+
processExecutionId: result?.processExecutionId || null,
|
|
110
|
+
workResult,
|
|
111
|
+
workResultError: result?.workResultError || null,
|
|
112
|
+
honesty: buildHonestyObservation(workResult)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function softWrapLifecycleAssertion(lifecycleAssertion, soft) {
|
|
117
|
+
if (!lifecycleAssertion) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
if (!(soft && !lifecycleAssertion.passed)) {
|
|
121
|
+
return lifecycleAssertion;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
...lifecycleAssertion,
|
|
125
|
+
passed: true,
|
|
126
|
+
soft: true,
|
|
127
|
+
warn: true,
|
|
128
|
+
message: `${lifecycleAssertion.message} (soft — warning only)`
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function collectCaseAssertions(expected, observation) {
|
|
133
|
+
const soft = expected.soft === true;
|
|
134
|
+
const observedCapabilities = observation.capabilities?.observed || [];
|
|
135
|
+
const assertions = [];
|
|
136
|
+
const lifecycleAssertion = softWrapLifecycleAssertion(
|
|
137
|
+
evaluateLifecycleStateExpectation(expected.lifecycleState, observation.lifecycleState),
|
|
138
|
+
soft
|
|
139
|
+
);
|
|
140
|
+
if (lifecycleAssertion) {
|
|
141
|
+
assertions.push(lifecycleAssertion);
|
|
142
|
+
}
|
|
143
|
+
assertions.push(
|
|
144
|
+
...evaluateCapabilityExpectations(expected?.capabilities?.required, observedCapabilities)
|
|
145
|
+
);
|
|
146
|
+
assertions.push(...evaluateFactExpectations(expected.facts, observation));
|
|
147
|
+
assertions.push(...evaluateProhibitedExpectations(expected.prohibited, observation));
|
|
148
|
+
assertions.push(...evaluateHonestyExpectations(expected.honesty, observation, { soft }));
|
|
149
|
+
return assertions;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function evaluateRoleAssistantTestCase(testCase, result, lifecycleVerdict, portalUrl) {
|
|
153
|
+
const observation = buildCaseObservation(result, lifecycleVerdict, portalUrl);
|
|
154
|
+
const assertions = collectCaseAssertions(testCase.expected || {}, observation);
|
|
155
|
+
const failed = assertions.filter((row) => !row.passed);
|
|
156
|
+
return {
|
|
157
|
+
caseId: testCase.caseId,
|
|
158
|
+
sourceFile: testCase.sourceFile,
|
|
159
|
+
passed: failed.length === 0,
|
|
160
|
+
lifecycleState: observation.lifecycleState,
|
|
161
|
+
assertions,
|
|
162
|
+
failedAssertions: failed,
|
|
163
|
+
warnings: assertions.filter((row) => row.warn === true),
|
|
164
|
+
observation
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
evaluateRoleAssistantTestCase
|
|
170
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft / structural honesty assertions for role-assistant test cases.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const HONESTY_COMPLETION_STATES = new Set([
|
|
8
|
+
'notEvaluated',
|
|
9
|
+
'waiting',
|
|
10
|
+
'completed',
|
|
11
|
+
'failed',
|
|
12
|
+
'cancelled',
|
|
13
|
+
'safeStopped'
|
|
14
|
+
]);
|
|
15
|
+
const HONESTY_GOAL_STATUSES = new Set([
|
|
16
|
+
'unverified',
|
|
17
|
+
'partiallySatisfied',
|
|
18
|
+
'satisfied',
|
|
19
|
+
'notSatisfied'
|
|
20
|
+
]);
|
|
21
|
+
const HONESTY_CONTRIB_STATUSES = new Set([
|
|
22
|
+
'notDefined',
|
|
23
|
+
'expected',
|
|
24
|
+
'pendingVerification',
|
|
25
|
+
'realized',
|
|
26
|
+
'notRealized',
|
|
27
|
+
'partial'
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
function emptyHonestyObservation() {
|
|
31
|
+
return {
|
|
32
|
+
present: false,
|
|
33
|
+
schemaVersion: null,
|
|
34
|
+
effectiveDepth: null,
|
|
35
|
+
completionState: null,
|
|
36
|
+
goalStatus: null,
|
|
37
|
+
contributionStatus: null,
|
|
38
|
+
resourceChangeCount: 0,
|
|
39
|
+
relationshipChangeCount: 0
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function asObject(value) {
|
|
44
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildHonestyObservation(workResult) {
|
|
48
|
+
if (!workResult || typeof workResult !== 'object') {
|
|
49
|
+
return emptyHonestyObservation();
|
|
50
|
+
}
|
|
51
|
+
const completion = asObject(workResult.completion);
|
|
52
|
+
const measurements = asObject(workResult.measurements);
|
|
53
|
+
const contribution = asObject(measurements.contributionObservation);
|
|
54
|
+
const businessChanges = asObject(workResult.businessChanges);
|
|
55
|
+
const resources = Array.isArray(businessChanges.resources) ? businessChanges.resources : [];
|
|
56
|
+
const relationships = Array.isArray(businessChanges.relationships)
|
|
57
|
+
? businessChanges.relationships
|
|
58
|
+
: [];
|
|
59
|
+
return {
|
|
60
|
+
present: true,
|
|
61
|
+
schemaVersion: workResult.schemaVersion || null,
|
|
62
|
+
effectiveDepth: workResult.effectiveDepth || null,
|
|
63
|
+
completionState: completion.state || null,
|
|
64
|
+
goalStatus: completion.goalStatus || null,
|
|
65
|
+
contributionStatus: contribution.status || null,
|
|
66
|
+
resourceChangeCount: resources.length,
|
|
67
|
+
relationshipChangeCount: relationships.length
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function pushHonestyAssertion(assertions, id, passed, message) {
|
|
72
|
+
assertions.push({ id, passed, message });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function assertWorkResultPresent(assertions, honesty, observation) {
|
|
76
|
+
const present = honesty.present === true;
|
|
77
|
+
const missingSuffix = observation.workResultError
|
|
78
|
+
? `: ${observation.workResultError}`
|
|
79
|
+
: '.';
|
|
80
|
+
pushHonestyAssertion(
|
|
81
|
+
assertions,
|
|
82
|
+
'expected.honesty.requireWorkResult',
|
|
83
|
+
present,
|
|
84
|
+
present
|
|
85
|
+
? 'work.result Output was present.'
|
|
86
|
+
: `work.result Output missing${missingSuffix}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function assertSchemaVersion(assertions, honestySpec, honesty) {
|
|
91
|
+
if (!honestySpec.schemaVersion) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const expected = String(honestySpec.schemaVersion);
|
|
95
|
+
const passed = String(honesty.schemaVersion || '') === expected;
|
|
96
|
+
pushHonestyAssertion(
|
|
97
|
+
assertions,
|
|
98
|
+
'expected.honesty.schemaVersion',
|
|
99
|
+
passed,
|
|
100
|
+
passed
|
|
101
|
+
? `schemaVersion matched "${expected}".`
|
|
102
|
+
: `schemaVersion expected "${expected}" but got ${JSON.stringify(honesty.schemaVersion)}.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function assertCompletion(assertions, honestySpec, honesty) {
|
|
107
|
+
if (honestySpec.requireCompletion === false) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const stateOk = HONESTY_COMPLETION_STATES.has(String(honesty.completionState || ''));
|
|
111
|
+
const goalOk = HONESTY_GOAL_STATUSES.has(String(honesty.goalStatus || ''));
|
|
112
|
+
pushHonestyAssertion(
|
|
113
|
+
assertions,
|
|
114
|
+
'expected.honesty.completion',
|
|
115
|
+
stateOk && goalOk,
|
|
116
|
+
stateOk && goalOk
|
|
117
|
+
? `completion.state/goalStatus honest (${honesty.completionState}/${honesty.goalStatus}).`
|
|
118
|
+
: `completion missing or invalid: state=${JSON.stringify(honesty.completionState)} goalStatus=${JSON.stringify(honesty.goalStatus)}.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function assertContribution(assertions, honestySpec, honesty) {
|
|
123
|
+
if (honestySpec.requireContributionObservation === false) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const ok = HONESTY_CONTRIB_STATUSES.has(String(honesty.contributionStatus || ''));
|
|
127
|
+
pushHonestyAssertion(
|
|
128
|
+
assertions,
|
|
129
|
+
'expected.honesty.contributionObservation',
|
|
130
|
+
ok,
|
|
131
|
+
ok
|
|
132
|
+
? `contributionObservation.status honest (${honesty.contributionStatus}).`
|
|
133
|
+
: `contributionObservation.status missing or invalid: ${JSON.stringify(honesty.contributionStatus)}.`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function assertMinResourceChanges(assertions, honestySpec, honesty, soft) {
|
|
138
|
+
if (typeof honestySpec.minResourceChanges !== 'number') {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const count = Number(honesty.resourceChangeCount || 0);
|
|
142
|
+
const passed = count >= honestySpec.minResourceChanges;
|
|
143
|
+
const softChange = soft || honestySpec.softResourceChanges === true;
|
|
144
|
+
pushHonestyAssertion(
|
|
145
|
+
assertions,
|
|
146
|
+
'expected.honesty.minResourceChanges',
|
|
147
|
+
softChange ? true : passed,
|
|
148
|
+
passed
|
|
149
|
+
? `resourceChangeCount ${count} >= ${honestySpec.minResourceChanges}.`
|
|
150
|
+
: `resourceChangeCount ${count} < ${honestySpec.minResourceChanges}${
|
|
151
|
+
softChange ? ' (soft — warning only)' : ''
|
|
152
|
+
}.`
|
|
153
|
+
);
|
|
154
|
+
if (softChange && !passed) {
|
|
155
|
+
assertions[assertions.length - 1].warn = true;
|
|
156
|
+
assertions[assertions.length - 1].soft = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function evaluateHonestyExpectations(honestySpec, observation, options = {}) {
|
|
161
|
+
if (!honestySpec || typeof honestySpec !== 'object' || Array.isArray(honestySpec)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const soft = options.soft === true;
|
|
165
|
+
const honesty = observation.honesty || {};
|
|
166
|
+
const assertions = [];
|
|
167
|
+
if (honestySpec.requireWorkResult !== false) {
|
|
168
|
+
assertWorkResultPresent(assertions, honesty, observation);
|
|
169
|
+
}
|
|
170
|
+
if (!honesty.present) {
|
|
171
|
+
// Missing Output is always a hard failure when required — soft does not waive it.
|
|
172
|
+
return assertions;
|
|
173
|
+
}
|
|
174
|
+
assertSchemaVersion(assertions, honestySpec, honesty);
|
|
175
|
+
assertCompletion(assertions, honestySpec, honesty);
|
|
176
|
+
assertContribution(assertions, honestySpec, honesty);
|
|
177
|
+
assertMinResourceChanges(assertions, honestySpec, honesty, soft);
|
|
178
|
+
return assertions;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
buildHonestyObservation,
|
|
183
|
+
evaluateHonestyExpectations
|
|
184
|
+
};
|