@aifabrix/builder 2.60.0 → 2.62.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 +14 -11
- package/docs/README.md +80 -0
- package/docs/builder-help/evidence-patterns.json +155 -0
- package/docs/builder-help/golden-examples/crm-company.json +30 -0
- package/docs/builder-help/golden-examples/crm-deal.json +29 -0
- package/docs/builder-help/golden-examples/document-storage-keyed-get.json +85 -0
- package/docs/builder-help/golden-examples/document-storage.json +30 -0
- package/docs/builder-help/golden-examples/meeting-transcript.json +29 -0
- package/docs/builder-help/golden-examples/repository-template.json +29 -0
- package/docs/builder-help/golden-examples/service-ticket.json +29 -0
- package/docs/builder-help/platform-roles.json +98 -0
- package/docs/builder-help/resource-type-catalog.json +402 -0
- package/lib/api/applications.api.js +22 -2
- package/lib/api/bootstrap-snapshot.api.js +115 -0
- package/lib/api/configuration.api.js +131 -0
- package/lib/api/system-secrets.api.js +72 -0
- package/lib/app/deploy.js +14 -5
- package/lib/app/run-docker-fallback.js +6 -1
- package/lib/app/run-env-compose.js +5 -6
- package/lib/app/run-helpers.js +2 -1
- package/lib/app/run-parameter-sync.js +142 -0
- package/lib/app/run.js +6 -4
- package/lib/build/docker-build-args.js +5 -2
- package/lib/build/index.js +3 -2
- package/lib/build/standard-docker-build.js +6 -2
- package/lib/channels/channel-artifact-generator.js +13 -3
- package/lib/cli/infra-guided-footers.js +10 -8
- package/lib/cli/infra-guided.js +30 -10
- package/lib/cli/setup-app.js +5 -0
- package/lib/cli/setup-environment.js +156 -0
- package/lib/cli/setup-infra-up-platform-action.js +14 -1
- package/lib/cli/setup-platform.js +2 -0
- package/lib/commands/auth-config.js +2 -0
- package/lib/commands/datasource-capability-upsert-cli.js +112 -0
- package/lib/commands/datasource-capability.js +4 -2
- package/lib/commands/env-secret-context.js +113 -0
- package/lib/commands/env-secret-list.js +200 -0
- package/lib/commands/env-secret-push-confirm.js +54 -0
- package/lib/commands/env-secret-push-run.js +227 -0
- package/lib/commands/env-secret-push.js +168 -0
- package/lib/commands/login.js +4 -0
- package/lib/commands/repair-datasource-apply.js +5 -1
- package/lib/commands/repair-datasource-keyed-document.js +122 -0
- package/lib/commands/repair-datasource-legacy.js +94 -0
- package/lib/commands/repair-datasource-run.js +1 -0
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/commands/setup-modes.js +8 -9
- package/lib/commands/setup-prompts-platform-mode.js +34 -0
- package/lib/commands/setup-prompts.js +2 -180
- package/lib/commands/setup.js +3 -0
- package/lib/commands/up-builder-api.js +24 -8
- package/lib/commands/up-common.js +16 -1
- package/lib/commands/up-dataplane-bootstrap.js +70 -14
- package/lib/commands/up-dataplane-credentials.js +21 -0
- package/lib/commands/up-dataplane.js +37 -18
- package/lib/commands/up-integration-server.js +112 -0
- package/lib/commands/verify-operations-skip-e2e.js +25 -1
- package/lib/commands/verify-operations-steps.js +13 -1
- package/lib/commands/wizard-config-normalizer.js +12 -5
- package/lib/commands/wizard-core.js +5 -157
- package/lib/commands/wizard-file-saving.js +163 -0
- package/lib/core/admin-secrets-env-overlay.js +8 -3
- package/lib/core/env-platform-expand.js +5 -1
- package/lib/core/environment-access-policy.js +121 -0
- package/lib/core/environment-mode-policy.js +177 -0
- package/lib/core/local-env-overrides.js +149 -0
- package/lib/core/secrets-ensure.js +4 -2
- package/lib/core/secrets-env-content.js +17 -10
- package/lib/core/secrets-env-write.js +11 -4
- package/lib/core/secrets-load.js +4 -2
- package/lib/datasource/binary-documents-validator.js +190 -0
- package/lib/datasource/capability/capability-manifest.js +1 -0
- package/lib/datasource/capability/run-capability-upsert.js +202 -0
- package/lib/datasource/capability/upsert-ingredients.js +291 -0
- package/lib/datasource/capability/upsert-operations.js +138 -0
- package/lib/datasource/capability/upsert-test-scaffold.js +189 -0
- package/lib/datasource/validate.js +12 -5
- package/lib/deployment/installation/index.js +14 -1
- package/lib/deployment/installation/infra-catalog.js +3 -0
- package/lib/deployment/installation/local-environment-stage.js +285 -0
- package/lib/deployment/installation/local-installation.js +76 -0
- package/lib/deployment/installation/stage-input.js +5 -1
- package/lib/generator/builders.js +3 -1
- package/lib/generator/index.js +3 -0
- package/lib/generator/split-variables.js +2 -2
- package/lib/lifecycle/product-model.js +4 -3
- package/lib/lifecycle/report-display.js +3 -2
- package/lib/parameters/database-adoption.js +125 -0
- package/lib/parameters/infra-parameter-catalog.js +1 -1
- package/lib/parameters/physical-database-naming.js +136 -0
- package/lib/parameters/physical-secret-name.js +194 -0
- package/lib/parameters/rsa-secret-values.js +165 -0
- package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +1 -1
- package/lib/programmatic/builder-help-governance.js +1 -1
- package/lib/programmatic/builder-help.js +1 -1
- package/lib/programmatic/run-channel-install-artifacts.js +28 -2
- package/lib/programmatic/run-channel-package.js +3 -1
- package/lib/programmatic/workspace-context.js +37 -2
- package/lib/role-assistant/test-runner-workhub-answers.js +4 -1
- package/lib/role-assistant/test-runner-workhub-missing-fields.js +42 -0
- package/lib/role-assistant/test-runner-workhub-wait-stop.js +94 -0
- package/lib/role-assistant/test-runner-workhub.js +28 -19
- package/lib/schema/application-schema.json +14 -2
- package/lib/schema/external-datasource.schema.json +23 -3
- package/lib/schema/infra.parameter.yaml +440 -56
- package/lib/schema/infrastructure-schema.json +213 -120
- package/lib/utils/compose-generate-docker-compose.js +9 -14
- package/lib/utils/compose-generator.js +4 -7
- package/lib/utils/compose-miso-env.js +2 -2
- package/lib/utils/compose-traefik-ingress-base.js +3 -4
- package/lib/utils/datasource-binary-evidence.js +92 -0
- package/lib/utils/datasource-test-run-capability-scope.js +44 -1
- package/lib/utils/datasource-test-run-debug-display.js +2 -0
- package/lib/utils/datasource-test-run-display.js +8 -2
- package/lib/utils/datasource-test-run-issue-guidance.js +176 -0
- package/lib/utils/datasource-test-run-tty-log.js +2 -0
- package/lib/utils/declarative-url-ports.js +3 -0
- package/lib/utils/env-copy.js +11 -10
- package/lib/utils/env-environment-file-paths.js +105 -0
- package/lib/utils/environment-scoped-resources.js +48 -14
- package/lib/utils/external-system-system-test-tty.js +3 -2
- package/lib/utils/image-tags.js +2 -2
- package/lib/utils/paths-system-builder-keys.js +7 -1
- package/lib/utils/paths.js +7 -2
- package/lib/utils/platform-kv-ref.js +1 -1
- package/lib/utils/postgres-platform-bootstrap.js +35 -1
- package/lib/utils/prepare-local-data-mount.js +58 -0
- package/lib/utils/redis-env-scope.js +21 -1
- package/lib/utils/registry-auth-sources.js +154 -0
- package/lib/utils/registry-credentials.js +131 -14
- package/lib/utils/secrets-generator.js +30 -21
- package/lib/utils/secrets-helpers.js +5 -8
- package/lib/utils/secrets-kv-scope.js +127 -28
- package/lib/utils/secrets-materialize-local.js +9 -0
- package/lib/utils/secrets-missing-error.js +35 -1
- package/lib/utils/system-secret-mapping.js +125 -0
- package/lib/utils/test-log-writer.js +2 -1
- package/lib/utils/token-manager.js +14 -8
- package/lib/utils/url-public-path-prefix.js +6 -10
- package/lib/validation/external-manifest-validator.js +5 -0
- package/lib/validation/openapi-contract-surface-validator.js +3 -1
- package/lib/validation/validate-external-file.js +5 -1
- package/package.json +6 -4
- package/templates/agent-kit/agent-kit.yaml +1 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +15 -0
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +3 -2
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +6 -2
- package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +22 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +7 -1
- package/templates/agent-kit/skills/aifabrix-role-assistant/references/testing-playbook.md +117 -0
- package/templates/agent-kit/skills/shared/interaction.md +83 -0
- package/templates/agent-kit/skills/shared/status.md +3 -12
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/builder-api/env.template +5 -1
- package/templates/applications/dataplane/application.yaml +2 -2
- package/templates/applications/dataplane/env.template +10 -12
- package/templates/applications/integration-server/application.yaml +91 -0
- package/templates/applications/integration-server/env.template +215 -0
- package/templates/applications/keycloak/application.yaml +6 -1
- package/templates/applications/mirrored-templates.json +52 -0
- package/templates/applications/miso-controller/application.yaml +116 -2
- package/templates/applications/miso-controller/env.template +62 -17
- package/templates/applications/miso-controller/rbac.yaml +17 -0
- package/templates/external-system/external-datasource.yaml.hbs +7 -1
- package/templates/marketplace/createUiDefinition.json +52 -4
- package/templates/marketplace/main.json +88 -10
- package/templates/python/docker-compose.hbs +1 -1
- package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +0 -41
- /package/{lib/programmatic/help-content → docs/builder-help/content}/channel-onboarding.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/cip-overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/connected-system-ui.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/dimensions-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/enterprise-sync-fabrix.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/subscription-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/workflow.md +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map an application's `.env` variables onto Miso catalog keys.
|
|
3
|
+
*
|
|
4
|
+
* The `.env` is keyed by environment variable (`MORI_POLICY_PRIVATE_KEY`) while the system
|
|
5
|
+
* secrets API is keyed by catalog key (`secrets-policyPrivateKeyVault`). The mapping is not
|
|
6
|
+
* invented here: it is read from the application's own `env.template`, because the
|
|
7
|
+
* `NAME=kv://<catalogKey>` line is already the declaration that decides where the value is
|
|
8
|
+
* read from at runtime. A second table in the CLI would be free to drift from it.
|
|
9
|
+
*
|
|
10
|
+
* A variable whose template line is not a `kv://` reference is ordinary configuration, not a
|
|
11
|
+
* secret, and is skipped rather than pushed.
|
|
12
|
+
*
|
|
13
|
+
* Note this is the flat `kv://<catalogKey>` form used by the Controller's system secrets.
|
|
14
|
+
* The Dataplane's credential store uses `kv://<systemKey>/<path>`; see
|
|
15
|
+
* `credential-secrets-env-kv.js` for that one. They are not interchangeable.
|
|
16
|
+
*
|
|
17
|
+
* @fileoverview env.template kv:// declarations → catalog keys
|
|
18
|
+
* @author AI Fabrix Team
|
|
19
|
+
* @version 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
/** `NAME=value`, ignoring blank lines, comments and `export ` prefixes. */
|
|
25
|
+
const ASSIGNMENT = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Strip surrounding quotes and trailing whitespace from an env value.
|
|
29
|
+
* @param {string} raw - Raw right-hand side
|
|
30
|
+
* @returns {string} Cleaned value
|
|
31
|
+
*/
|
|
32
|
+
function cleanValue(raw) {
|
|
33
|
+
const trimmed = String(raw === undefined || raw === null ? '' : raw).trim();
|
|
34
|
+
if (trimmed.length >= 2) {
|
|
35
|
+
const first = trimmed[0];
|
|
36
|
+
const last = trimmed[trimmed.length - 1];
|
|
37
|
+
if ((first === '"' && last === '"') || (first === '\'' && last === '\'')) {
|
|
38
|
+
return trimmed.slice(1, -1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return trimmed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse `NAME=value` lines into a Map, last assignment winning.
|
|
46
|
+
* @param {string} content - File content
|
|
47
|
+
* @returns {Map<string, string>} Variable name → value
|
|
48
|
+
*/
|
|
49
|
+
function parseEnvAssignments(content) {
|
|
50
|
+
const out = new Map();
|
|
51
|
+
for (const line of String(content || '').split(/\r?\n/)) {
|
|
52
|
+
if (!line || /^\s*#/.test(line)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const match = line.match(ASSIGNMENT);
|
|
56
|
+
if (match) {
|
|
57
|
+
out.set(match[1], cleanValue(match[2]));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Catalog keys declared by an `env.template`, keyed by environment variable name.
|
|
65
|
+
* Only `kv://` lines are secrets; everything else is skipped.
|
|
66
|
+
* @param {string} templateContent - env.template content
|
|
67
|
+
* @returns {Map<string, string>} Variable name → catalog key
|
|
68
|
+
*/
|
|
69
|
+
function catalogKeysFromTemplate(templateContent) {
|
|
70
|
+
const out = new Map();
|
|
71
|
+
for (const [name, value] of parseEnvAssignments(templateContent)) {
|
|
72
|
+
if (!value.startsWith('kv://')) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const catalogKey = value.slice('kv://'.length).trim();
|
|
76
|
+
if (catalogKey && !catalogKey.includes('/')) {
|
|
77
|
+
out.set(name, catalogKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Pair the values in a `.env` with the catalog keys its `env.template` declares.
|
|
85
|
+
*
|
|
86
|
+
* Skipped rows are returned rather than dropped, so the command can say why a variable was
|
|
87
|
+
* not pushed instead of silently ignoring it.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} envContent - Operator's .env content
|
|
90
|
+
* @param {string} templateContent - The application's env.template content
|
|
91
|
+
* @param {Object} [options] - `only` restricts to one variable name
|
|
92
|
+
* @returns {{ pushable: Array<Object>, skipped: Array<Object> }} Rows carrying value and catalog key
|
|
93
|
+
*/
|
|
94
|
+
function mapEnvToCatalogKeys(envContent, templateContent, options = {}) {
|
|
95
|
+
const declared = catalogKeysFromTemplate(templateContent);
|
|
96
|
+
const values = parseEnvAssignments(envContent);
|
|
97
|
+
const only = options.only ? String(options.only).trim() : '';
|
|
98
|
+
const pushable = [];
|
|
99
|
+
const skipped = [];
|
|
100
|
+
for (const [name, value] of values) {
|
|
101
|
+
if (only && name !== only) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const catalogKey = declared.get(name);
|
|
105
|
+
if (!catalogKey) {
|
|
106
|
+
skipped.push({ name, reason: 'not a kv:// reference in env.template' });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!value) {
|
|
110
|
+
skipped.push({ name, catalogKey, reason: 'no value in the env file' });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
pushable.push({ name, catalogKey, value });
|
|
114
|
+
}
|
|
115
|
+
if (only && pushable.length === 0 && skipped.length === 0) {
|
|
116
|
+
skipped.push({ name: only, reason: 'not present in the env file' });
|
|
117
|
+
}
|
|
118
|
+
return { pushable, skipped };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
parseEnvAssignments,
|
|
123
|
+
catalogKeysFromTemplate,
|
|
124
|
+
mapEnvToCatalogKeys
|
|
125
|
+
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs').promises;
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const { sanitizeBinaryEvidence } = require('./datasource-binary-evidence');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Prepare object for JSON serialization (handles circular refs)
|
|
@@ -45,7 +46,7 @@ async function writeTestLog(appKey, data, logType = 'test-integration', integrat
|
|
|
45
46
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
46
47
|
const filename = `${logType}-${timestamp}.json`;
|
|
47
48
|
const filePath = path.join(logsDir, filename);
|
|
48
|
-
const sanitized = sanitizeForLog(data);
|
|
49
|
+
const sanitized = sanitizeForLog(sanitizeBinaryEvidence(data));
|
|
49
50
|
await fs.writeFile(filePath, JSON.stringify(sanitized, null, 2), 'utf8');
|
|
50
51
|
return filePath;
|
|
51
52
|
}
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const config = require('../core/config');
|
|
13
|
-
const logger = require('./logger');
|
|
14
13
|
const { maskSensitiveData } = require('./log-redaction');
|
|
15
14
|
const {
|
|
16
15
|
refreshClientToken,
|
|
@@ -205,12 +204,17 @@ async function tryClientTokenAuth(environment, appName, controllerUrl) {
|
|
|
205
204
|
return null;
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
// Two causes reach here: no credentials at all, or credentials the controller rejected.
|
|
208
|
+
// Saying "put it in secrets.local.yaml" for the second sends the reader after a key that is
|
|
209
|
+
// already present and simply not valid, so name the rejection instead.
|
|
210
|
+
function throwNoDeploymentAuth(credentialApp, exchangeError) {
|
|
211
|
+
const err = new Error(exchangeError
|
|
212
|
+
? `Client credentials for '${credentialApp}' were rejected by the controller: ${exchangeError.message}. ` +
|
|
213
|
+
`Re-issue them with: aifabrix login && aifabrix app rotate-secret ${credentialApp}`
|
|
214
|
+
: `No authentication method available. For CI: set AIFABRIX_DEPLOYMENT_AUTH=client-credentials and MISO_CLIENTID/MISO_CLIENTSECRET in .env or '${credentialApp}-client-idKeyVault' in secrets.local.yaml. For interactive use: aifabrix login`);
|
|
212
215
|
err.statusCode = 401;
|
|
213
216
|
err.authFailure = true;
|
|
217
|
+
if (exchangeError) err.cause = exchangeError;
|
|
214
218
|
throw err;
|
|
215
219
|
}
|
|
216
220
|
|
|
@@ -262,8 +266,10 @@ async function getDeploymentAuth(controllerUrl, environment, appName, options =
|
|
|
262
266
|
controller: controllerUrl
|
|
263
267
|
};
|
|
264
268
|
}
|
|
265
|
-
} catch {
|
|
266
|
-
//
|
|
269
|
+
} catch (err) {
|
|
270
|
+
// Keep it: credentials existed and the controller turned them down, which is a
|
|
271
|
+
// different problem from having none, and the only place that fact is observable.
|
|
272
|
+
throwNoDeploymentAuth(credentialApp, err);
|
|
267
273
|
}
|
|
268
274
|
}
|
|
269
275
|
|
|
@@ -341,7 +347,7 @@ async function forceRefreshDeviceToken(controllerUrl) {
|
|
|
341
347
|
|
|
342
348
|
// Must have refresh token to force refresh
|
|
343
349
|
if (!tokenInfo.refreshToken) {
|
|
344
|
-
|
|
350
|
+
warnRefreshFailureOnce(controllerUrl, 'Cannot refresh: no refresh token available. Please login again using: aifabrix login');
|
|
345
351
|
return null;
|
|
346
352
|
}
|
|
347
353
|
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plan 117 URL path prefix for url://public (
|
|
2
|
+
* Plan 117 URL path prefix for url://public (scoped environments only when gated).
|
|
3
3
|
*
|
|
4
|
-
* @fileoverview baseEffective ∧ derived envKey ∈
|
|
4
|
+
* @fileoverview baseEffective ∧ derived envKey ∈ SCOPED_RUN_ENVIRONMENTS. Caller must pass prefix only when `traefik` is on (see url-declarative-resolve).
|
|
5
5
|
* @author AI Fabrix Team
|
|
6
6
|
* @version 1.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
+
const { isScopedRunEnvironmentKey } = require('./environment-scoped-resources');
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* @param {boolean} useEnvironmentScopedResources - config gate
|
|
13
15
|
* @param {boolean} appEnvironmentScopedResources - application.yaml
|
|
14
16
|
* @param {string} derivedEnvKey - from client id (dev|tst|pro|miso)
|
|
15
|
-
* @returns {string} '' | '/dev' | '/tst'
|
|
17
|
+
* @returns {string} '' | '/dev' | '/tst' | '/pro'
|
|
16
18
|
*/
|
|
17
19
|
function computePublicUrlPathPrefix(useEnvironmentScopedResources, appEnvironmentScopedResources, derivedEnvKey) {
|
|
18
20
|
const baseEffective = Boolean(useEnvironmentScopedResources) && Boolean(appEnvironmentScopedResources);
|
|
@@ -20,13 +22,7 @@ function computePublicUrlPathPrefix(useEnvironmentScopedResources, appEnvironmen
|
|
|
20
22
|
return '';
|
|
21
23
|
}
|
|
22
24
|
const k = String(derivedEnvKey || '').toLowerCase();
|
|
23
|
-
|
|
24
|
-
return '/dev';
|
|
25
|
-
}
|
|
26
|
-
if (k === 'tst') {
|
|
27
|
-
return '/tst';
|
|
28
|
-
}
|
|
29
|
-
return '';
|
|
25
|
+
return isScopedRunEnvironmentKey(k) ? `/${k}` : '';
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
module.exports = {
|
|
@@ -16,6 +16,7 @@ const path = require('path');
|
|
|
16
16
|
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
17
17
|
const { validateFieldReferences } = require('../datasource/field-reference-validator');
|
|
18
18
|
const { validateAbac } = require('../datasource/abac-validator');
|
|
19
|
+
const { validateBinaryDocumentsContract } = require('../datasource/binary-documents-validator');
|
|
19
20
|
const { validateManifestRuntimeConfiguration } = require('./runtime-configuration-rules');
|
|
20
21
|
const { validateAppContractSurface } = require('./openapi-contract-surface-validator');
|
|
21
22
|
|
|
@@ -147,9 +148,13 @@ function validateDatasources(manifest, ajv, externalDatasourceSchema, errors, wa
|
|
|
147
148
|
} else {
|
|
148
149
|
const fieldRefErrors = validateFieldReferences(datasource);
|
|
149
150
|
const abacErrors = validateAbac(datasource);
|
|
151
|
+
const binaryDocuments = validateBinaryDocumentsContract(datasource);
|
|
150
152
|
const prefix = `Datasource ${index + 1} (${datasource.key || 'unknown'}): `;
|
|
151
153
|
fieldRefErrors.forEach(e => errors.push(prefix + e));
|
|
152
154
|
abacErrors.forEach(e => errors.push(prefix + e));
|
|
155
|
+
// Codes are the dataplane's, so the offline answer matches the upload answer.
|
|
156
|
+
binaryDocuments.errors.forEach(e => errors.push(`${prefix}${e.code}: ${e.message}`));
|
|
157
|
+
binaryDocuments.warnings.forEach(w => warnings.push(`${prefix}${w.code}: ${w.message}`));
|
|
153
158
|
}
|
|
154
159
|
});
|
|
155
160
|
}
|
|
@@ -426,5 +426,7 @@ module.exports = {
|
|
|
426
426
|
validateDatasourceFileContractSurface,
|
|
427
427
|
validateVendorOperationContract,
|
|
428
428
|
validateServiceContractDatasourceContracts,
|
|
429
|
-
findVendorOperation
|
|
429
|
+
findVendorOperation,
|
|
430
|
+
resolveVendorOpenApiSpec,
|
|
431
|
+
inferIntegrationContextFromPath
|
|
430
432
|
};
|
|
@@ -111,11 +111,15 @@ function validateDatasourceFileExtras(parsed, filePath) {
|
|
|
111
111
|
const { collectExternalDatasourceWarnings } = require('./datasource-warnings');
|
|
112
112
|
const { validateDatasourceFileContractSurface } = require('./openapi-contract-surface-validator');
|
|
113
113
|
const errors = [...validateFieldReferences(parsed), ...validateAbac(parsed)];
|
|
114
|
+
const { validateBinaryDocumentsContract } = require('../datasource/binary-documents-validator');
|
|
115
|
+
const binary = validateBinaryDocumentsContract(parsed);
|
|
116
|
+
errors.push(...binary.errors.map(issue => `${issue.code}: ${issue.message}`));
|
|
114
117
|
const contractSurface = validateDatasourceFileContractSurface(parsed, filePath);
|
|
115
118
|
errors.push(...contractSurface.errors);
|
|
116
119
|
const warnings = [
|
|
117
120
|
...collectExternalDatasourceWarnings(parsed),
|
|
118
|
-
...contractSurface.warnings
|
|
121
|
+
...contractSurface.warnings,
|
|
122
|
+
...binary.warnings.map(issue => `${issue.code}: ${issue.message}`)
|
|
119
123
|
];
|
|
120
124
|
return { errors, warnings };
|
|
121
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.62.0",
|
|
4
4
|
"description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/aifabrix.js",
|
|
12
12
|
"lib/",
|
|
13
|
+
"docs/builder-help/",
|
|
13
14
|
"templates/",
|
|
14
15
|
"README.md",
|
|
15
16
|
"LICENSE"
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"dev-deploy": "node scripts/pnpm/dev-deploy.mjs",
|
|
25
26
|
"af": "node scripts/pnpm/af.mjs",
|
|
26
27
|
"check-quiet": "node scripts/pnpm/check-quiet.mjs",
|
|
27
|
-
"agent-skills:sync": "node scripts/pnpm/agent-skills-sync.mjs",
|
|
28
|
+
"agent-skills:sync": "node scripts/pnpm/agent-skills-sync.mjs --codex-home",
|
|
28
29
|
"agent-skills:check": "node scripts/pnpm/agent-skills-sync.mjs --check",
|
|
29
30
|
"agent-skills:sync:codex": "node scripts/pnpm/agent-skills-sync.mjs --codex-home",
|
|
30
31
|
"agent-skills:test": "node --test scripts/pnpm/agent-skills-sync.test.mjs",
|
|
@@ -34,11 +35,12 @@
|
|
|
34
35
|
"lint:fix": "eslint . --ext .js --fix",
|
|
35
36
|
"test": "node tests/scripts/test-wrapper.js",
|
|
36
37
|
"build": "npm run lint && npm run test",
|
|
37
|
-
"build:ci": "npm run lint && npm run check:schema-sync && npm run check:flags && npm run check:cli-help && npm run test:ci",
|
|
38
|
+
"build:ci": "npm run lint && npm run check:schema-sync && npm run check:flags && npm run check:cli-help && npm run check:mirrored-templates && npm run test:ci",
|
|
38
39
|
"test:ci": "bash tests/scripts/ci-simulate.sh",
|
|
39
40
|
"check:schema-sync": "node scripts/check-datasource-test-run-schema-sync.js",
|
|
40
41
|
"check:flags": "jest tests/lib/schema/flag-map-validation-run.test.js --runInBand --config jest.config.default.js",
|
|
41
42
|
"check:cli-help": "node scripts/check-cli-help-examples.js",
|
|
43
|
+
"check:mirrored-templates": "node scripts/check-mirrored-templates.js",
|
|
42
44
|
"test:manual": "jest --config jest.config.manual.js --runInBand",
|
|
43
45
|
"test:integration": "jest --config jest.config.integration.js --runInBand",
|
|
44
46
|
"test:integration:fixtures": "jest --config jest.config.integration.fixtures.js --runInBand",
|
|
@@ -131,7 +133,7 @@
|
|
|
131
133
|
}
|
|
132
134
|
},
|
|
133
135
|
"dependencies": {
|
|
134
|
-
"@aifabrix/miso-client": "
|
|
136
|
+
"@aifabrix/miso-client": "5.0.3",
|
|
135
137
|
"@azure/identity": "4.11.1",
|
|
136
138
|
"adm-zip": "^0.6.1",
|
|
137
139
|
"ajv": "^8.20.0",
|
|
@@ -20,7 +20,7 @@ Resolve phase: explicit argument → ask. Package state informs the recommendati
|
|
|
20
20
|
the action.
|
|
21
21
|
|
|
22
22
|
Stop at the phase gate. Do not run the next phase without `next-yes`
|
|
23
|
-
([interaction.md](../
|
|
23
|
+
([interaction.md](../shared/interaction.md)).
|
|
24
24
|
|
|
25
25
|
| Phase | Entry | Success |
|
|
26
26
|
| --- | --- | --- |
|
|
@@ -67,6 +67,7 @@ System key: argument → active plan → sole `integration/` folder → ask once
|
|
|
67
67
|
|
|
68
68
|
## References
|
|
69
69
|
|
|
70
|
+
- [interaction.md](../shared/interaction.md) — native selection controls, routes, and phase gates
|
|
70
71
|
- [status.md](../shared/status.md) — read-only default, route options, status matrix
|
|
71
72
|
- [delivery-gates.md](references/delivery-gates.md) — BID/wizard/repair, upload vs deploy, tests, identity, protection
|
|
72
73
|
- [contract-checklist.md](references/contract-checklist.md) — exposed, FK, viewpoints, RBAC/ABAC
|
|
@@ -39,6 +39,21 @@ If `integration/<systemKey>/deploy.js` exists, you may run it as the publish lad
|
|
|
39
39
|
|
|
40
40
|
Never mark live E2E pass when it did not run.
|
|
41
41
|
|
|
42
|
+
## Upsert authoring and warnings
|
|
43
|
+
|
|
44
|
+
When upsert is explicitly required, first run
|
|
45
|
+
`aifabrix datasource capability upsert <file-or-key> --dry-run --json`. Apply a
|
|
46
|
+
fix automatically only when the finding identifies a deterministic local
|
|
47
|
+
manifest change and the active phase authorizes mutation. Rerun the exact
|
|
48
|
+
failed command before broader validation.
|
|
49
|
+
|
|
50
|
+
Every open finding must state the observed condition, business consequence,
|
|
51
|
+
safe action, prevention, residual risk, and exact rerun. A source API that
|
|
52
|
+
cannot constrain the complete business identity is not AI-fixable: keep valid
|
|
53
|
+
separate create/update capabilities, report duplicate or wrong-update risk,
|
|
54
|
+
and withhold verified upsert. Never invent a lookup, weaken the two-write
|
|
55
|
+
no-duplicate assertion, or hide the warning behind another passing test.
|
|
56
|
+
|
|
42
57
|
## Identity and protection order
|
|
43
58
|
|
|
44
59
|
1. `aifabrix auth status` — if unauthenticated, [login.md](../../shared/login.md)
|
|
@@ -16,7 +16,7 @@ Named with no specification, report what exists and ask; do not start writing a
|
|
|
16
16
|
an inferred spec ([status.md](../shared/status.md)).
|
|
17
17
|
|
|
18
18
|
Score completeness with [challenge-checklist.md](references/challenge-checklist.md).
|
|
19
|
-
|
|
19
|
+
Structured choices and phase gates: [interaction.md](../shared/interaction.md).
|
|
20
20
|
Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
|
|
21
21
|
CLI login from chat: [login.md](../shared/login.md).
|
|
22
22
|
|
|
@@ -32,7 +32,8 @@ Do not start Connected System validate until **PLAN_READY** and the user picks `
|
|
|
32
32
|
|
|
33
33
|
1. Collect the spec the user named. Do not mine platform repos as the spec.
|
|
34
34
|
2. Inventory without adding: purpose, sources, entities, relationships, role labels, operations, Viewpoints, Evidence, identity join. Missing sections → `TBD` plus an Open question.
|
|
35
|
-
3. Challenge required rows.
|
|
35
|
+
3. Challenge required rows. Use the structured question control for every MISSING required
|
|
36
|
+
row. Authority and identity cannot be guessed.
|
|
36
37
|
4. Write or update `.cursor/plans/{major}.0-{slug}.plan.md` using the template H2 order. Same capability updates in place.
|
|
37
38
|
5. Report status and path. On PLAN_READY, offer validate — do not run it without `next-yes`.
|
|
38
39
|
|
|
@@ -38,8 +38,10 @@ business-case readiness. None of them, alone or together, makes prove READY.
|
|
|
38
38
|
|
|
39
39
|
Every phase **must** update [learning-template.md](references/learning-template.md).
|
|
40
40
|
Evidence rules: [evidence-lifecycle.md](references/evidence-lifecycle.md).
|
|
41
|
+
Role Assistant test execution and diagnosis:
|
|
42
|
+
[testing-playbook.md](../aifabrix-role-assistant/references/testing-playbook.md).
|
|
41
43
|
Demo shape: [demo-template.md](references/demo-template.md).
|
|
42
|
-
|
|
44
|
+
Structured choices and phase gates: [interaction.md](../shared/interaction.md).
|
|
43
45
|
Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
|
|
44
46
|
CLI login from chat: [login.md](../shared/login.md).
|
|
45
47
|
|
|
@@ -60,7 +62,9 @@ Repeatable: demo, Knowledge, candidate Evidence, tests. Do not refuse because va
|
|
|
60
62
|
|
|
61
63
|
## Prove
|
|
62
64
|
|
|
63
|
-
Run assistant tests
|
|
65
|
+
Run assistant tests using the testing playbook. Record the business verdict,
|
|
66
|
+
execution reference, and gap owner (`none` | `product-gap` |
|
|
67
|
+
`missing-customer-fact` | `scope-decision`).
|
|
64
68
|
Happy + safe-stop coverage for authority scenarios.
|
|
65
69
|
Never report candidates as certified.
|
|
66
70
|
|
|
@@ -49,6 +49,25 @@ Promote candidate → governed Evidence only when:
|
|
|
49
49
|
3. Contract validation + certification path for Evidence completed (public Evidence Fabrix / operate-first RA docs)
|
|
50
50
|
4. learning.md records promotion date + commit
|
|
51
51
|
|
|
52
|
+
## Human authority and applied proof
|
|
53
|
+
|
|
54
|
+
Keep these states distinct:
|
|
55
|
+
|
|
56
|
+
| State | What it proves |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| Candidate or proposal | Draft content exists; it is not governed Evidence |
|
|
59
|
+
| Certified | The applicable human governance decision completed |
|
|
60
|
+
| Active | The product may supply the Evidence to execution |
|
|
61
|
+
| Applied | A recorded execution shows the Evidence affected the result |
|
|
62
|
+
|
|
63
|
+
An agent may prepare candidates, execute authorized source cases, and inspect a
|
|
64
|
+
proposal. It must pause before certifying, activating, rejecting, or
|
|
65
|
+
deactivating Evidence unless the current user explicitly authorizes that action
|
|
66
|
+
and the product workflow permits it. Record the pause as `BLOCKED_BY_HUMAN`, not
|
|
67
|
+
as a failed test. After the human gate, rerun the case and prove Applied Evidence
|
|
68
|
+
from execution evidence; catalog presence is not Applied proof. Follow the
|
|
69
|
+
[Role Assistant testing playbook](../../aifabrix-role-assistant/references/testing-playbook.md).
|
|
70
|
+
|
|
52
71
|
## Tests
|
|
53
72
|
|
|
54
73
|
For each in-scope scenario ID:
|
|
@@ -66,3 +85,6 @@ Prefer `role-assistant/ra-<roleKey>/tests/<suiteId>/` in this customer repo (pac
|
|
|
66
85
|
- Hiding process law in Knowledge
|
|
67
86
|
- Uploading candidates as if certified
|
|
68
87
|
- Skipping safe-stop coverage for authority scenarios
|
|
88
|
+
- Certifying, activating, rejecting, or deactivating Evidence without the
|
|
89
|
+
current user's explicit authority
|
|
90
|
+
- Reporting catalog presence as proof that Evidence was Applied
|
|
@@ -9,8 +9,11 @@ description: >
|
|
|
9
9
|
# aifabrix-role-assistant
|
|
10
10
|
|
|
11
11
|
See [package-boundary.md](references/package-boundary.md) and nested `role-assistant/AGENTS.md`.
|
|
12
|
+
For test execution, verdicts, fixtures, and diagnosis, follow
|
|
13
|
+
[testing-playbook.md](references/testing-playbook.md).
|
|
12
14
|
Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
|
|
13
15
|
CLI login from chat: [login.md](../shared/login.md).
|
|
16
|
+
Structured choices and phase gates: [interaction.md](../shared/interaction.md).
|
|
14
17
|
|
|
15
18
|
Named with no task, report status and stop: [status.md](../shared/status.md).
|
|
16
19
|
|
|
@@ -47,7 +50,9 @@ substitutes for it.
|
|
|
47
50
|
|
|
48
51
|
1. `aifabrix download <catalogRoleOrRaKey>` before editing an existing assistant.
|
|
49
52
|
2. Author settings, Knowledge, Evidence, tests, and sidecars per current Builder contracts. Read `aifabrix role-assistant --help`.
|
|
50
|
-
3. `aifabrix role-assistant test …` is allowed without local RA publish.
|
|
53
|
+
3. `aifabrix role-assistant test …` is allowed without local RA publish. Apply
|
|
54
|
+
the testing playbook and inspect hard business assertions before reporting
|
|
55
|
+
READY or VERIFIED.
|
|
51
56
|
4. Upload/deploy RA only when the user explicitly asks in this turn.
|
|
52
57
|
5. Report results honestly.
|
|
53
58
|
|
|
@@ -62,3 +67,4 @@ substitutes for it.
|
|
|
62
67
|
## Trigger examples
|
|
63
68
|
|
|
64
69
|
- Create or update the Finance Controller Role Assistant
|
|
70
|
+
- Test or diagnose the Finance Controller Role Assistant
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Role Assistant testing and diagnosis
|
|
2
|
+
|
|
3
|
+
Public concepts: https://docs.aifabrix.ai/docs/role-assistants
|
|
4
|
+
Exact commands, fields, and flags: run `aifabrix role-assistant test --help` with
|
|
5
|
+
the installed Builder version.
|
|
6
|
+
|
|
7
|
+
Use this playbook for live Role Assistant cases. A successful command is not
|
|
8
|
+
enough: the observed business outcome, authority boundary, capabilities, Work
|
|
9
|
+
Result, and resource changes must match the case.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
- Authenticate to the intended environment and identify the package and cases
|
|
14
|
+
root before execution.
|
|
15
|
+
- Confirm every required Connected System is published and certified, its
|
|
16
|
+
required capabilities are available, and worker availability is known.
|
|
17
|
+
- Record the actor and business role, mutation scope, designated test subjects,
|
|
18
|
+
and cleanup rule before a mutating case.
|
|
19
|
+
- Treat missing auth, worker availability, capability, fixture, or required
|
|
20
|
+
human action as `BLOCKED` with the exact prerequisite.
|
|
21
|
+
|
|
22
|
+
## Execution ladder
|
|
23
|
+
|
|
24
|
+
1. Validate the package and use `--list-cases` to confirm discovery.
|
|
25
|
+
2. Run one focused case with `--case ... --json`.
|
|
26
|
+
3. Inspect lifecycle and final Runtime status, questions or approvals,
|
|
27
|
+
`expected.capabilities`, Work Result completion, resource changes, and
|
|
28
|
+
Evidence use. Do not rely on the process exit or wrapper verdict alone.
|
|
29
|
+
4. Correct the owning layer, rerun the exact case, and then run its suite.
|
|
30
|
+
5. Run broader suites only after the focused case is stable.
|
|
31
|
+
6. Record environment, timestamp, package/source revision, command,
|
|
32
|
+
execution/correlation reference, verdict class, and gap owner in
|
|
33
|
+
`learning/learning.md` or the plan-defined evidence location.
|
|
34
|
+
|
|
35
|
+
## Verdict law
|
|
36
|
+
|
|
37
|
+
| Observation | Verdict |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| Hard assertions and intended business outcome pass | `PASS` |
|
|
40
|
+
| Intended denial or safe stop occurs with no forbidden mutation | `PASS_EXPECTED_STOP` |
|
|
41
|
+
| `expected.soft: true` turns a mismatch into a warning | `GAP`; never `VERIFIED` |
|
|
42
|
+
| A prerequisite or required human action is absent | `BLOCKED` with the missing row |
|
|
43
|
+
| Runtime or CLI is defective and the run is reproducible | `PRODUCT_GAP` |
|
|
44
|
+
| Role Assistant, Knowledge, or Evidence content is wrong while the product behaves correctly | `PACKAGE_GAP` |
|
|
45
|
+
|
|
46
|
+
An exit code `0`, CLI `PASS`, lifecycle smoke, or certification level cannot
|
|
47
|
+
replace the applicable business verdict. An expected safe stop is not ordinary
|
|
48
|
+
success: name the denied or waiting state and prove that no forbidden mutation
|
|
49
|
+
occurred.
|
|
50
|
+
|
|
51
|
+
## Assertion quality
|
|
52
|
+
|
|
53
|
+
- Release and prove cases use hard lifecycle and Result assertions. Soft cases
|
|
54
|
+
are exploratory and cannot establish READY or VERIFIED.
|
|
55
|
+
- Mutation cases assert the exact required and forbidden capabilities,
|
|
56
|
+
`expected.honesty.requireWorkResult`, completion, and a meaningful
|
|
57
|
+
`expected.honesty.minResourceChanges` value.
|
|
58
|
+
- Read-only cases forbid mutation capabilities. Authority cases assert the
|
|
59
|
+
intended denial, question, approval wait, or safe stop.
|
|
60
|
+
- Do not weaken an assertion to accommodate a failure. Classify the gap and
|
|
61
|
+
repair its owner.
|
|
62
|
+
|
|
63
|
+
## Fixtures, identity, and cleanup
|
|
64
|
+
|
|
65
|
+
- Stable shared identities are read-only unless the integration README names
|
|
66
|
+
them as approved mutable subjects.
|
|
67
|
+
- Give mutable cases collision-safe explicit values. Generate and write those
|
|
68
|
+
values into the case through normal repository editing before the run; do not
|
|
69
|
+
claim that an interpolation token exists unless current CLI help, schema,
|
|
70
|
+
source, and tests establish it.
|
|
71
|
+
- Share an identity across cases only for a documented suite dependency.
|
|
72
|
+
- Never inject a hidden primary key, actor, business role, API key, or
|
|
73
|
+
authorization dimension to make a case pass.
|
|
74
|
+
- Cleanup must be explicit, bounded to the designated subject, and separately
|
|
75
|
+
authorized when destructive.
|
|
76
|
+
|
|
77
|
+
## ABAC and replay
|
|
78
|
+
|
|
79
|
+
Every authority-sensitive scenario needs an allowed case and an outside-scope
|
|
80
|
+
or denied case. Nested calls must preserve the same actor and business-role
|
|
81
|
+
context. Ambiguous identity must ask or stop safely, never select a convenient
|
|
82
|
+
record. Retry and replay cases must prove there is no duplicate resource and no
|
|
83
|
+
double-counted Evidence.
|
|
84
|
+
|
|
85
|
+
## Human Evidence boundary
|
|
86
|
+
|
|
87
|
+
Agents may author candidates, run source executions, and inspect proposals when
|
|
88
|
+
authorized. They must not certify, activate, reject, or deactivate Evidence for
|
|
89
|
+
a human unless the current user explicitly authorizes that product action and
|
|
90
|
+
the product workflow permits it. A run paused at certification or activation is
|
|
91
|
+
`BLOCKED_BY_HUMAN`, not failed. Later proof must show Evidence was actually
|
|
92
|
+
Applied in execution; catalog presence alone is insufficient. See
|
|
93
|
+
[Evidence lifecycle](../../aifabrix-prove/references/evidence-lifecycle.md).
|
|
94
|
+
|
|
95
|
+
## Root-cause routing
|
|
96
|
+
|
|
97
|
+
| Finding | Owner / next action |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| Auth, environment, or worker unavailable | Report the exact prerequisite |
|
|
100
|
+
| Duplicate or stale test data | Fixture owner; preserve the trail and repair safely |
|
|
101
|
+
| Soft or incomplete expectation | Case author; strengthen without changing product behavior |
|
|
102
|
+
| Missing operation, certification, or capability | Connected System package |
|
|
103
|
+
| Runtime loop, wrong identity binding, or false success | Product gap with execution evidence |
|
|
104
|
+
| Wrong prompt, Knowledge, or Evidence definition | Role Assistant package |
|
|
105
|
+
| Evidence certification or activation required | Human governance gate |
|
|
106
|
+
|
|
107
|
+
Do not conceal a lower-layer gap in Knowledge or build a Role Assistant-local
|
|
108
|
+
imitation of a missing Connected System capability.
|
|
109
|
+
|
|
110
|
+
For an upsert finding, route local manifest repair to Connected System
|
|
111
|
+
ownership and rerun the exact failed proof. If the external source cannot
|
|
112
|
+
support exact business-identity lookup or uniqueness, state what happened,
|
|
113
|
+
what cannot be repaired by AI, the duplicate or wrong-record-update risk, the
|
|
114
|
+
safe alternative, and how future connectors should prevent it. Keep readiness
|
|
115
|
+
blocked or qualified as reported by validation; never substitute create,
|
|
116
|
+
weaken the no-duplicate assertion, or clear the warning because an unrelated
|
|
117
|
+
case passed.
|