arkgate 2.12.0 → 2.13.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/CHANGELOG.md +83 -0
- package/README.md +55 -31
- package/bin/ark-check.mjs +95 -36
- package/bin/ark-mcp.mjs +11 -5
- package/bin/ark-shared.mjs +88 -56
- package/bin/ark.mjs +45 -10
- package/bin/lib/agent-gates.mjs +12 -0
- package/bin/lib/architecture-scan.mjs +8 -0
- package/bin/lib/ci-and-commands.mjs +9 -3
- package/bin/lib/codex-home.mjs +7 -0
- package/bin/lib/config-contract.mjs +331 -0
- package/bin/lib/doctor-plan.mjs +43 -16
- package/bin/lib/enforcement-profiles.mjs +97 -0
- package/bin/lib/host-support-matrix.mjs +77 -0
- package/bin/lib/install-migrate.mjs +45 -14
- package/bin/lib/mcp-adoption.mjs +35 -3
- package/bin/lib/open-html.mjs +75 -0
- package/bin/lib/presets.mjs +3 -2
- package/bin/lib/safety-diagnostics.mjs +31 -11
- package/bin/lib/skill-install.mjs +64 -0
- package/bin/lib/ts-resolve.mjs +2 -1
- package/bin/lib/weakest-link.mjs +417 -0
- package/bin/lib/write-path-capabilities.mjs +182 -0
- package/bin/lib/write-path-detect.mjs +62 -99
- package/dist/configContract-iBLxx5Tz.d.cts +53 -0
- package/dist/configContract-iBLxx5Tz.d.ts +53 -0
- package/dist/eslint/index.cjs +375 -13
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.cts +30 -20
- package/dist/eslint/index.d.ts +30 -20
- package/dist/eslint/index.js +375 -13
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +723 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -5
- package/dist/index.d.ts +95 -5
- package/dist/index.js +716 -61
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +150 -42
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +2 -1
- package/dist/nestjs/index.d.ts +2 -1
- package/dist/nestjs/index.js +150 -42
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +723 -61
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +3 -2
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +716 -61
- package/dist/runtime/index.js.map +1 -1
- package/dist/{types-BZ17b9i5.d.cts → types-BxBwnBpC.d.cts} +9 -36
- package/dist/{types-BZ17b9i5.d.ts → types-Wcs_l1_J.d.ts} +9 -36
- package/docs/agent-guide.md +32 -20
- package/docs/ai-gates.md +53 -18
- package/docs/configuration.md +97 -0
- package/docs/enthusiast/README.md +3 -3
- package/docs/enthusiast/how-to-agent-gates.md +7 -3
- package/docs/migrate-from-ark-runtime-kernel.md +3 -0
- package/docs/package-surface.md +14 -9
- package/docs/production-hardening.md +15 -2
- package/docs/threat-model.md +65 -0
- package/docs/typescript-support.md +3 -3
- package/package.json +15 -2
- package/schemas/ark.config.schema.json +750 -0
- package/server.json +2 -2
- package/templates/hooks/pre-commit-ark +37 -0
- package/templates/skills/ark-coverage.md +2 -2
- package/templates/skills/ark-runtime.md +8 -5
- package/templates/skills/ark-upgrade.md +36 -16
- package/tests/fixtures/ts-consumer/ark.config.json +2 -0
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ANALYSIS_IR_SCHEMA_VERSION: () => ANALYSIS_IR_SCHEMA_VERSION,
|
|
23
24
|
DEFAULT_MAX_HISTORY_SIZE: () => DEFAULT_MAX_HISTORY_SIZE,
|
|
24
25
|
EventContractRegistryImpl: () => EventContractRegistryImpl,
|
|
25
26
|
EventContractViolationError: () => EventContractViolationError,
|
|
@@ -37,6 +38,8 @@ __export(index_exports, {
|
|
|
37
38
|
SourceMetadataOverrideError: () => SourceMetadataOverrideError,
|
|
38
39
|
UnknownEventSourceError: () => UnknownEventSourceError,
|
|
39
40
|
UnregisteredIntentError: () => UnregisteredIntentError,
|
|
41
|
+
analyzeChange: () => analyzeChange,
|
|
42
|
+
analyzeProject: () => analyzeProject,
|
|
40
43
|
architecturalPolicies: () => architecturalPolicies,
|
|
41
44
|
buildPublishPolicyContext: () => buildPublishPolicyContext,
|
|
42
45
|
checkAdapterGovernance: () => checkAdapterGovernance,
|
|
@@ -71,8 +74,12 @@ __export(index_exports, {
|
|
|
71
74
|
definePolicy: () => definePolicy,
|
|
72
75
|
definePort: () => definePort,
|
|
73
76
|
definePublishPolicy: () => definePublishPolicy,
|
|
77
|
+
deterministicHash: () => deterministicHash,
|
|
74
78
|
elevenLayerProfile: () => elevenLayerProfile,
|
|
79
|
+
explainViolation: () => explainViolation,
|
|
75
80
|
isLayerPolicy: () => isLayerPolicy,
|
|
81
|
+
loadContract: () => loadContract,
|
|
82
|
+
stableSerialize: () => stableSerialize,
|
|
76
83
|
syncRegistryToGraph: () => syncRegistryToGraph,
|
|
77
84
|
validateIntentName: () => validateIntentName,
|
|
78
85
|
version: () => version
|
|
@@ -80,7 +87,7 @@ __export(index_exports, {
|
|
|
80
87
|
module.exports = __toCommonJS(index_exports);
|
|
81
88
|
|
|
82
89
|
// src/version.ts
|
|
83
|
-
var version = "2.
|
|
90
|
+
var version = "2.13.0";
|
|
84
91
|
|
|
85
92
|
// src/kernel/intent/IntentRegistry.ts
|
|
86
93
|
var IntentRegistry = class {
|
|
@@ -744,6 +751,10 @@ async function recordInterceptorError(interceptor, event, error, deps) {
|
|
|
744
751
|
}
|
|
745
752
|
|
|
746
753
|
// src/domain/layerMatch.ts
|
|
754
|
+
var regexpCache = /* @__PURE__ */ new Map();
|
|
755
|
+
function escapeLiteral(ch) {
|
|
756
|
+
return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
757
|
+
}
|
|
747
758
|
function normalizeGlobSeparators(pattern) {
|
|
748
759
|
let out = "";
|
|
749
760
|
for (let i = 0; i < pattern.length; i += 1) {
|
|
@@ -762,6 +773,91 @@ function normalizeGlobSeparators(pattern) {
|
|
|
762
773
|
}
|
|
763
774
|
return out;
|
|
764
775
|
}
|
|
776
|
+
function bracesBalanced(glob) {
|
|
777
|
+
let depth = 0;
|
|
778
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
779
|
+
const c = glob[i];
|
|
780
|
+
if (c === "\\") {
|
|
781
|
+
i += 1;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (c === "{") depth += 1;
|
|
785
|
+
else if (c === "}") {
|
|
786
|
+
depth -= 1;
|
|
787
|
+
if (depth < 0) return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return depth === 0;
|
|
791
|
+
}
|
|
792
|
+
function globToRegExp(pattern) {
|
|
793
|
+
const cached = regexpCache.get(pattern);
|
|
794
|
+
if (cached) return cached;
|
|
795
|
+
const glob = normalizeGlobSeparators(pattern);
|
|
796
|
+
const useBraces = bracesBalanced(glob);
|
|
797
|
+
let out = "";
|
|
798
|
+
let braceDepth = 0;
|
|
799
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
800
|
+
const c = glob[i];
|
|
801
|
+
if (c === "\\" && i + 1 < glob.length) {
|
|
802
|
+
out += escapeLiteral(glob[i + 1]);
|
|
803
|
+
i += 1;
|
|
804
|
+
} else if (c === "*") {
|
|
805
|
+
if (glob[i + 1] === "*") {
|
|
806
|
+
if (glob[i + 2] === "/") {
|
|
807
|
+
out += "(?:.*/)?";
|
|
808
|
+
i += 2;
|
|
809
|
+
} else {
|
|
810
|
+
out += ".*";
|
|
811
|
+
i += 1;
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
out += "[^/]*";
|
|
815
|
+
}
|
|
816
|
+
} else if (c === "?") {
|
|
817
|
+
out += "[^/]";
|
|
818
|
+
} else if (c === "{" && useBraces) {
|
|
819
|
+
out += "(?:";
|
|
820
|
+
braceDepth += 1;
|
|
821
|
+
} else if (c === "}" && useBraces && braceDepth > 0) {
|
|
822
|
+
out += ")";
|
|
823
|
+
braceDepth -= 1;
|
|
824
|
+
} else if (c === "," && useBraces && braceDepth > 0) {
|
|
825
|
+
out += "|";
|
|
826
|
+
} else {
|
|
827
|
+
out += escapeLiteral(c);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const re = new RegExp(`^${out}$`);
|
|
831
|
+
regexpCache.set(pattern, re);
|
|
832
|
+
return re;
|
|
833
|
+
}
|
|
834
|
+
function patternSpecificity(pattern) {
|
|
835
|
+
const glob = normalizeGlobSeparators(String(pattern));
|
|
836
|
+
const beforeWildcard = glob.split("*")[0];
|
|
837
|
+
const literalSegments = beforeWildcard.split("/").filter(Boolean).length;
|
|
838
|
+
const literalLength = glob.replace(/\*/g, "").length;
|
|
839
|
+
return literalSegments * 1e4 + literalLength;
|
|
840
|
+
}
|
|
841
|
+
function layerForRelativePath(relPath, layers) {
|
|
842
|
+
const rel = String(relPath).split(/[/\\]/).join("/");
|
|
843
|
+
let bestName;
|
|
844
|
+
let bestScore = -1;
|
|
845
|
+
for (const layer of layers ?? []) {
|
|
846
|
+
if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
for (const pattern of layer.patterns ?? []) {
|
|
850
|
+
if (globToRegExp(pattern).test(rel)) {
|
|
851
|
+
const score = patternSpecificity(pattern);
|
|
852
|
+
if (score > bestScore) {
|
|
853
|
+
bestScore = score;
|
|
854
|
+
bestName = layer.name;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return bestName;
|
|
860
|
+
}
|
|
765
861
|
function sliceIdForPath(relPath, sliceFolders) {
|
|
766
862
|
if (!sliceFolders?.length) return void 0;
|
|
767
863
|
const parts = String(relPath).split(/[/\\]/).filter(Boolean);
|
|
@@ -1356,13 +1452,13 @@ function formatPath(parent, field) {
|
|
|
1356
1452
|
return parent ? `${parent}.${field}` : field;
|
|
1357
1453
|
}
|
|
1358
1454
|
function validateSchemaField(value, fieldSchema, path, contract, issues) {
|
|
1359
|
-
const
|
|
1360
|
-
if (fieldSchema.type !== "unknown" &&
|
|
1455
|
+
const valueType2 = actualType(value);
|
|
1456
|
+
if (fieldSchema.type !== "unknown" && valueType2 !== fieldSchema.type) {
|
|
1361
1457
|
issues.push({
|
|
1362
1458
|
intent: contract.intent,
|
|
1363
1459
|
version: contract.version,
|
|
1364
1460
|
field: path,
|
|
1365
|
-
message: `Expected ${fieldSchema.type}, received ${
|
|
1461
|
+
message: `Expected ${fieldSchema.type}, received ${valueType2}.`
|
|
1366
1462
|
});
|
|
1367
1463
|
return;
|
|
1368
1464
|
}
|
|
@@ -1919,6 +2015,321 @@ function syncRegistryToGraph(registry, graph, options = {}) {
|
|
|
1919
2015
|
}
|
|
1920
2016
|
}
|
|
1921
2017
|
|
|
2018
|
+
// src/domain/configContract.ts
|
|
2019
|
+
var ARK_CONFIG_SCHEMA_VERSION = "1.0";
|
|
2020
|
+
var ARK_CONFIG_SCHEMA_URL = "https://unpkg.com/arkgate@2/schemas/ark.config.schema.json";
|
|
2021
|
+
var DEFAULT_LAYER_NAMES = [
|
|
2022
|
+
"DomainModel",
|
|
2023
|
+
"ApplicationOrchestration",
|
|
2024
|
+
"PersistenceAdapters",
|
|
2025
|
+
"IntegrationAdapters",
|
|
2026
|
+
"WorkflowSagaEngine",
|
|
2027
|
+
"BackgroundJobsScheduling",
|
|
2028
|
+
"PresentationAdapters",
|
|
2029
|
+
"ReportingReadModels",
|
|
2030
|
+
"ExtensibilityMetadata",
|
|
2031
|
+
"SecurityAuditObservability",
|
|
2032
|
+
"Kernel"
|
|
2033
|
+
];
|
|
2034
|
+
var DEFAULT_ALLOWED_FLOWS = /* @__PURE__ */ new Set([
|
|
2035
|
+
"PresentationAdapters->ApplicationOrchestration",
|
|
2036
|
+
"ApplicationOrchestration->DomainModel",
|
|
2037
|
+
"WorkflowSagaEngine->ApplicationOrchestration",
|
|
2038
|
+
"WorkflowSagaEngine->DomainModel",
|
|
2039
|
+
"BackgroundJobsScheduling->ApplicationOrchestration"
|
|
2040
|
+
]);
|
|
2041
|
+
function createDefaultRules() {
|
|
2042
|
+
const rules = [];
|
|
2043
|
+
for (const from of DEFAULT_LAYER_NAMES) {
|
|
2044
|
+
for (const to of DEFAULT_LAYER_NAMES) {
|
|
2045
|
+
if (from === to || DEFAULT_ALLOWED_FLOWS.has(`${from}->${to}`)) continue;
|
|
2046
|
+
rules.push({ from, to, allowed: false });
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return rules;
|
|
2050
|
+
}
|
|
2051
|
+
var DEFAULT_ARK_CONFIG_RULES = createDefaultRules();
|
|
2052
|
+
var stringArraySchema = {
|
|
2053
|
+
type: "array",
|
|
2054
|
+
items: { type: "string", minLength: 1 },
|
|
2055
|
+
uniqueItems: true
|
|
2056
|
+
};
|
|
2057
|
+
var ARK_CONFIG_SCHEMA = {
|
|
2058
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
2059
|
+
$id: ARK_CONFIG_SCHEMA_URL,
|
|
2060
|
+
title: "ArkGate architecture contract",
|
|
2061
|
+
description: "Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",
|
|
2062
|
+
type: "object",
|
|
2063
|
+
additionalProperties: false,
|
|
2064
|
+
required: ["$schema", "schemaVersion", "include", "layers", "rules"],
|
|
2065
|
+
properties: {
|
|
2066
|
+
$schema: {
|
|
2067
|
+
type: "string",
|
|
2068
|
+
minLength: 1,
|
|
2069
|
+
default: ARK_CONFIG_SCHEMA_URL,
|
|
2070
|
+
description: "Editor-facing URL or local path for this JSON Schema."
|
|
2071
|
+
},
|
|
2072
|
+
schemaVersion: {
|
|
2073
|
+
type: "string",
|
|
2074
|
+
const: ARK_CONFIG_SCHEMA_VERSION,
|
|
2075
|
+
default: ARK_CONFIG_SCHEMA_VERSION
|
|
2076
|
+
},
|
|
2077
|
+
name: { type: "string", minLength: 1 },
|
|
2078
|
+
include: { ...stringArraySchema, minItems: 1, default: ["src"] },
|
|
2079
|
+
exclude: { ...stringArraySchema, default: [] },
|
|
2080
|
+
excludeGenerated: { type: "boolean", default: true },
|
|
2081
|
+
frameworkOverlay: { type: "string", minLength: 1 },
|
|
2082
|
+
layers: {
|
|
2083
|
+
type: "array",
|
|
2084
|
+
default: [],
|
|
2085
|
+
items: { $ref: "#/$defs/layer" }
|
|
2086
|
+
},
|
|
2087
|
+
rules: {
|
|
2088
|
+
type: "array",
|
|
2089
|
+
default: DEFAULT_ARK_CONFIG_RULES,
|
|
2090
|
+
items: { $ref: "#/$defs/rule" }
|
|
2091
|
+
},
|
|
2092
|
+
cyclePolicy: {
|
|
2093
|
+
type: "string",
|
|
2094
|
+
enum: ["strict", "soft", "framework-soft", "off"],
|
|
2095
|
+
default: "strict"
|
|
2096
|
+
},
|
|
2097
|
+
dynamicImportAllowlist: { ...stringArraySchema, default: [] },
|
|
2098
|
+
safety: {
|
|
2099
|
+
$ref: "#/$defs/safety",
|
|
2100
|
+
default: {
|
|
2101
|
+
maxTsSuppressions: 0,
|
|
2102
|
+
maxAnyCasts: 0,
|
|
2103
|
+
allowInMemory: false,
|
|
2104
|
+
allowDisabledPeerIsolation: false
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
},
|
|
2108
|
+
$defs: {
|
|
2109
|
+
layer: {
|
|
2110
|
+
type: "object",
|
|
2111
|
+
additionalProperties: false,
|
|
2112
|
+
required: ["name", "patterns"],
|
|
2113
|
+
properties: {
|
|
2114
|
+
name: { type: "string", minLength: 1 },
|
|
2115
|
+
patterns: { ...stringArraySchema, minItems: 1 },
|
|
2116
|
+
exclude: stringArraySchema,
|
|
2117
|
+
intentPrefixes: stringArraySchema,
|
|
2118
|
+
description: { type: "string", minLength: 1 },
|
|
2119
|
+
forbiddenGlobals: stringArraySchema,
|
|
2120
|
+
mayImportInfrastructure: { type: "boolean" },
|
|
2121
|
+
optional: { type: "boolean" }
|
|
2122
|
+
}
|
|
2123
|
+
},
|
|
2124
|
+
rule: {
|
|
2125
|
+
type: "object",
|
|
2126
|
+
additionalProperties: false,
|
|
2127
|
+
required: ["from", "to", "allowed"],
|
|
2128
|
+
properties: {
|
|
2129
|
+
from: { type: "string", minLength: 1 },
|
|
2130
|
+
to: { type: "string", minLength: 1 },
|
|
2131
|
+
allowed: { type: "boolean" },
|
|
2132
|
+
message: { type: "string", minLength: 1 },
|
|
2133
|
+
peerIsolation: { type: "boolean" },
|
|
2134
|
+
sliceFolders: { ...stringArraySchema, minItems: 1 }
|
|
2135
|
+
}
|
|
2136
|
+
},
|
|
2137
|
+
safety: {
|
|
2138
|
+
type: "object",
|
|
2139
|
+
additionalProperties: false,
|
|
2140
|
+
properties: {
|
|
2141
|
+
maxTsSuppressions: { type: "integer", minimum: 0, default: 0 },
|
|
2142
|
+
maxAnyCasts: { type: "integer", minimum: 0, default: 0 },
|
|
2143
|
+
allowInMemory: { type: "boolean", default: false },
|
|
2144
|
+
allowDisabledPeerIsolation: { type: "boolean", default: false }
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
var ArkConfigValidationError = class extends Error {
|
|
2150
|
+
issues;
|
|
2151
|
+
source;
|
|
2152
|
+
constructor(source, issues) {
|
|
2153
|
+
super(
|
|
2154
|
+
`Invalid ArkGate config (${source}):
|
|
2155
|
+
${issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n")}`
|
|
2156
|
+
);
|
|
2157
|
+
this.name = "ArkConfigValidationError";
|
|
2158
|
+
this.source = source;
|
|
2159
|
+
this.issues = issues;
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
function isObject(value) {
|
|
2163
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2164
|
+
}
|
|
2165
|
+
function propertyPath(parent, key) {
|
|
2166
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
|
|
2167
|
+
}
|
|
2168
|
+
function valueType(value) {
|
|
2169
|
+
if (value === null) return "null";
|
|
2170
|
+
if (Array.isArray(value)) return "array";
|
|
2171
|
+
return typeof value;
|
|
2172
|
+
}
|
|
2173
|
+
function resolveSchemaRef(ref, root) {
|
|
2174
|
+
const prefix = "#/$defs/";
|
|
2175
|
+
if (!ref.startsWith(prefix)) return void 0;
|
|
2176
|
+
return root.$defs[ref.slice(prefix.length)];
|
|
2177
|
+
}
|
|
2178
|
+
function validateNode(value, schema, path, root, issues) {
|
|
2179
|
+
if (schema.$ref) {
|
|
2180
|
+
const referenced = resolveSchemaRef(schema.$ref, root);
|
|
2181
|
+
if (!referenced) {
|
|
2182
|
+
issues.push({ path, message: `schema reference ${schema.$ref} cannot be resolved` });
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
validateNode(value, referenced, path, root, issues);
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
if (schema.const !== void 0 && !Object.is(value, schema.const)) {
|
|
2189
|
+
issues.push({ path, message: `must equal ${JSON.stringify(schema.const)}` });
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
|
|
2193
|
+
issues.push({ path, message: `must be one of ${schema.enum.map(String).join(", ")}` });
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (schema.type === "object") {
|
|
2197
|
+
if (!isObject(value)) {
|
|
2198
|
+
issues.push({ path, message: `must be an object; received ${valueType(value)}` });
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
const properties = schema.properties ?? {};
|
|
2202
|
+
for (const key of schema.required ?? []) {
|
|
2203
|
+
if (value[key] === void 0) {
|
|
2204
|
+
issues.push({ path: propertyPath(path, key), message: "is required" });
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
if (schema.additionalProperties === false) {
|
|
2208
|
+
for (const key of Object.keys(value)) {
|
|
2209
|
+
if (!(key in properties)) {
|
|
2210
|
+
issues.push({ path: propertyPath(path, key), message: "unknown field" });
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
for (const [key, childSchema] of Object.entries(properties)) {
|
|
2215
|
+
if (value[key] !== void 0) {
|
|
2216
|
+
validateNode(value[key], childSchema, propertyPath(path, key), root, issues);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
if (schema.type === "array") {
|
|
2222
|
+
if (!Array.isArray(value)) {
|
|
2223
|
+
issues.push({ path, message: `must be an array; received ${valueType(value)}` });
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
2227
|
+
issues.push({ path, message: `must contain at least ${schema.minItems} item(s)` });
|
|
2228
|
+
}
|
|
2229
|
+
if (schema.uniqueItems) {
|
|
2230
|
+
const serialized = value.map((entry) => JSON.stringify(entry));
|
|
2231
|
+
if (new Set(serialized).size !== serialized.length) {
|
|
2232
|
+
issues.push({ path, message: "must not contain duplicate items" });
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
if (schema.items) {
|
|
2236
|
+
value.forEach(
|
|
2237
|
+
(entry, index) => validateNode(entry, schema.items, `${path}[${index}]`, root, issues)
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
if (schema.type === "string") {
|
|
2243
|
+
if (typeof value !== "string") {
|
|
2244
|
+
issues.push({ path, message: `must be a string; received ${valueType(value)}` });
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
2248
|
+
issues.push({ path, message: `must contain at least ${schema.minLength} character(s)` });
|
|
2249
|
+
}
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
if (schema.type === "boolean") {
|
|
2253
|
+
if (typeof value !== "boolean") {
|
|
2254
|
+
issues.push({ path, message: `must be a boolean; received ${valueType(value)}` });
|
|
2255
|
+
}
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
if (schema.type === "integer") {
|
|
2259
|
+
if (!Number.isInteger(value)) {
|
|
2260
|
+
issues.push({ path, message: `must be an integer; received ${valueType(value)}` });
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
2264
|
+
issues.push({ path, message: `must be at least ${schema.minimum}` });
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function defaultedConfig(input) {
|
|
2269
|
+
return {
|
|
2270
|
+
...input,
|
|
2271
|
+
$schema: input.$schema === void 0 ? ARK_CONFIG_SCHEMA_URL : input.$schema,
|
|
2272
|
+
schemaVersion: input.schemaVersion === void 0 ? ARK_CONFIG_SCHEMA_VERSION : input.schemaVersion,
|
|
2273
|
+
include: input.include === void 0 ? ["src"] : input.include,
|
|
2274
|
+
layers: input.layers === void 0 ? [] : input.layers,
|
|
2275
|
+
rules: input.rules === void 0 ? DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule })) : input.rules
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
function migrateArkConfig(input, source = "ark.config.json") {
|
|
2279
|
+
if (!isObject(input)) {
|
|
2280
|
+
throw new ArkConfigValidationError(source, [
|
|
2281
|
+
{ path: "$", message: `must be an object; received ${valueType(input)}` }
|
|
2282
|
+
]);
|
|
2283
|
+
}
|
|
2284
|
+
const migratedFrom = input.schemaVersion === void 0 ? "unversioned" : null;
|
|
2285
|
+
if (input.schemaVersion !== void 0 && input.schemaVersion !== ARK_CONFIG_SCHEMA_VERSION) {
|
|
2286
|
+
throw new ArkConfigValidationError(source, [
|
|
2287
|
+
{
|
|
2288
|
+
path: "$.schemaVersion",
|
|
2289
|
+
message: `unsupported version ${JSON.stringify(input.schemaVersion)}; expected ${ARK_CONFIG_SCHEMA_VERSION}`
|
|
2290
|
+
}
|
|
2291
|
+
]);
|
|
2292
|
+
}
|
|
2293
|
+
return { candidate: defaultedConfig(input), migratedFrom };
|
|
2294
|
+
}
|
|
2295
|
+
function loadArkConfigContract(input, source = "ark.config.json") {
|
|
2296
|
+
const { candidate, migratedFrom } = migrateArkConfig(input, source);
|
|
2297
|
+
const issues = [];
|
|
2298
|
+
validateNode(
|
|
2299
|
+
candidate,
|
|
2300
|
+
ARK_CONFIG_SCHEMA,
|
|
2301
|
+
"$",
|
|
2302
|
+
ARK_CONFIG_SCHEMA,
|
|
2303
|
+
issues
|
|
2304
|
+
);
|
|
2305
|
+
if (issues.length > 0) throw new ArkConfigValidationError(source, issues);
|
|
2306
|
+
return { config: candidate, migratedFrom };
|
|
2307
|
+
}
|
|
2308
|
+
function parseArkConfigJson(json, source = "ark.config.json") {
|
|
2309
|
+
let input;
|
|
2310
|
+
try {
|
|
2311
|
+
input = JSON.parse(json);
|
|
2312
|
+
} catch (error) {
|
|
2313
|
+
throw new ArkConfigValidationError(source, [
|
|
2314
|
+
{
|
|
2315
|
+
path: "$",
|
|
2316
|
+
message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
2317
|
+
}
|
|
2318
|
+
]);
|
|
2319
|
+
}
|
|
2320
|
+
return loadArkConfigContract(input, source);
|
|
2321
|
+
}
|
|
2322
|
+
function withArkConfigMetadata(config) {
|
|
2323
|
+
const result = {
|
|
2324
|
+
$schema: typeof config.$schema === "string" && config.$schema.length > 0 ? config.$schema : ARK_CONFIG_SCHEMA_URL,
|
|
2325
|
+
schemaVersion: ARK_CONFIG_SCHEMA_VERSION
|
|
2326
|
+
};
|
|
2327
|
+
for (const [key, value] of Object.entries(config)) {
|
|
2328
|
+
if (key !== "$schema" && key !== "schemaVersion") result[key] = value;
|
|
2329
|
+
}
|
|
2330
|
+
return result;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
1922
2333
|
// src/kernel/layers/ArchitectureProfile.ts
|
|
1923
2334
|
function normalizePrefix(prefix) {
|
|
1924
2335
|
return prefix.endsWith(".") ? prefix : `${prefix}.`;
|
|
@@ -1928,21 +2339,6 @@ function byLongestPrefix(a, b) {
|
|
|
1928
2339
|
const maxB = b.prefixes.length ? Math.max(...b.prefixes.map((p) => p.length)) : 0;
|
|
1929
2340
|
return maxB - maxA;
|
|
1930
2341
|
}
|
|
1931
|
-
function flowKey2(from, to) {
|
|
1932
|
-
return `${from}->${to}`;
|
|
1933
|
-
}
|
|
1934
|
-
function createStrictDenyRules(layers, allowedFlows) {
|
|
1935
|
-
const allowed = new Set(allowedFlows.map((flow) => flowKey2(flow.from, flow.to)));
|
|
1936
|
-
const rules = [];
|
|
1937
|
-
for (const from of layers) {
|
|
1938
|
-
for (const to of layers) {
|
|
1939
|
-
if (from.name === to.name) continue;
|
|
1940
|
-
if (allowed.has(flowKey2(from.name, to.name))) continue;
|
|
1941
|
-
rules.push({ from: from.name, to: to.name, allowed: false });
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
return rules;
|
|
1945
|
-
}
|
|
1946
2342
|
function createArchitectureProfile(options) {
|
|
1947
2343
|
const layers = options.layers.map((layer) => ({
|
|
1948
2344
|
...layer,
|
|
@@ -2041,20 +2437,10 @@ var elevenLayerProfileLayers = [
|
|
|
2041
2437
|
order: 11
|
|
2042
2438
|
}
|
|
2043
2439
|
];
|
|
2044
|
-
var elevenLayerAllowedFlows = [
|
|
2045
|
-
{ from: "PresentationAdapters", to: "ApplicationOrchestration" },
|
|
2046
|
-
{ from: "ApplicationOrchestration", to: "DomainModel" },
|
|
2047
|
-
{ from: "WorkflowSagaEngine", to: "ApplicationOrchestration" },
|
|
2048
|
-
{ from: "WorkflowSagaEngine", to: "DomainModel" },
|
|
2049
|
-
{ from: "BackgroundJobsScheduling", to: "ApplicationOrchestration" }
|
|
2050
|
-
];
|
|
2051
2440
|
var elevenLayerProfile = createArchitectureProfile({
|
|
2052
2441
|
name: "Ark 11-layer Hexagonal Event-Driven Profile",
|
|
2053
2442
|
layers: elevenLayerProfileLayers,
|
|
2054
|
-
rules:
|
|
2055
|
-
elevenLayerProfileLayers,
|
|
2056
|
-
elevenLayerAllowedFlows
|
|
2057
|
-
)
|
|
2443
|
+
rules: DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule }))
|
|
2058
2444
|
});
|
|
2059
2445
|
var defaultElevenLayerDirectories = {
|
|
2060
2446
|
DomainModel: ["domain"],
|
|
@@ -2078,7 +2464,7 @@ function createElevenLayerArkConfig(options = {}) {
|
|
|
2078
2464
|
const rootDir = options.rootDir ?? "src";
|
|
2079
2465
|
const optional = options.optionalLayers ?? true;
|
|
2080
2466
|
const prefix = rootDir === "." ? "" : `${rootDir}/`;
|
|
2081
|
-
return {
|
|
2467
|
+
return withArkConfigMetadata({
|
|
2082
2468
|
include: options.include ?? [rootDir],
|
|
2083
2469
|
layers: elevenLayerProfile.layers.map((layer) => ({
|
|
2084
2470
|
name: layer.name,
|
|
@@ -2089,7 +2475,7 @@ function createElevenLayerArkConfig(options = {}) {
|
|
|
2089
2475
|
optional
|
|
2090
2476
|
})),
|
|
2091
2477
|
rules: [...elevenLayerProfile.rules]
|
|
2092
|
-
};
|
|
2478
|
+
});
|
|
2093
2479
|
}
|
|
2094
2480
|
|
|
2095
2481
|
// src/kernel/metadata/MetadataRegistry.ts
|
|
@@ -2293,7 +2679,11 @@ function extractModuleSpecifiersAst(ts, source) {
|
|
|
2293
2679
|
"export",
|
|
2294
2680
|
Boolean(node.isTypeOnly || specifiersOnly)
|
|
2295
2681
|
);
|
|
2296
|
-
} else if (ts.
|
|
2682
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
|
|
2683
|
+
const argument = node.moduleReference.expression;
|
|
2684
|
+
const value = tsStringLiteralText(ts, argument);
|
|
2685
|
+
if (value !== void 0) push(argument, value, "require");
|
|
2686
|
+
} else if (ts.isCallExpression(node)) {
|
|
2297
2687
|
const argument = node.arguments[0];
|
|
2298
2688
|
const value = tsStringLiteralText(ts, argument);
|
|
2299
2689
|
if (value !== void 0 && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
@@ -2307,17 +2697,25 @@ function extractModuleSpecifiersAst(ts, source) {
|
|
|
2307
2697
|
visit(sourceFile);
|
|
2308
2698
|
return matches.sort((a, b) => a.index - b.index);
|
|
2309
2699
|
}
|
|
2310
|
-
function
|
|
2700
|
+
function nonLiteralDynamicDependencies(ts, source) {
|
|
2311
2701
|
const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
|
|
2312
|
-
const
|
|
2702
|
+
const uses = [];
|
|
2313
2703
|
const visit = (node) => {
|
|
2314
|
-
if (ts.isCallExpression(node)
|
|
2315
|
-
|
|
2704
|
+
if (ts.isCallExpression(node)) {
|
|
2705
|
+
const dynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
2706
|
+
const directRequire = ts.isIdentifier(node.expression) && node.expression.text === "require";
|
|
2707
|
+
const argument = node.arguments[0];
|
|
2708
|
+
if ((dynamicImport || directRequire) && (!argument || !ts.isStringLiteralLike(argument))) {
|
|
2709
|
+
uses.push({
|
|
2710
|
+
line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
2711
|
+
kind: directRequire ? "require" : "import"
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2316
2714
|
}
|
|
2317
2715
|
ts.forEachChild(node, visit);
|
|
2318
2716
|
};
|
|
2319
2717
|
visit(sourceFile);
|
|
2320
|
-
return
|
|
2718
|
+
return uses;
|
|
2321
2719
|
}
|
|
2322
2720
|
function extractQuotedStringsAst(ts, source) {
|
|
2323
2721
|
const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
|
|
@@ -2428,10 +2826,45 @@ function tsPublishSourceLiteral(ts, node) {
|
|
|
2428
2826
|
const rawMetadata = tsObjectPropertyValue(ts, firstArg, "metadata");
|
|
2429
2827
|
return tsStringLiteralText(ts, tsObjectPropertyValue(ts, rawMetadata, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, secondArg, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, thirdArg, "source"));
|
|
2430
2828
|
}
|
|
2829
|
+
function singleFileTypeChecker(ts, sourceFile) {
|
|
2830
|
+
const options = {
|
|
2831
|
+
noLib: true,
|
|
2832
|
+
noResolve: true,
|
|
2833
|
+
target: ts.ScriptTarget.Latest
|
|
2834
|
+
};
|
|
2835
|
+
const host = ts.createCompilerHost(options, true);
|
|
2836
|
+
host.getSourceFile = (fileName) => fileName === sourceFile.fileName ? sourceFile : void 0;
|
|
2837
|
+
host.fileExists = (fileName) => fileName === sourceFile.fileName;
|
|
2838
|
+
host.readFile = (fileName) => fileName === sourceFile.fileName ? sourceFile.text : void 0;
|
|
2839
|
+
return ts.createProgram([sourceFile.fileName], options, host).getTypeChecker();
|
|
2840
|
+
}
|
|
2841
|
+
function propertyAccessPath(ts, node) {
|
|
2842
|
+
const segments = [];
|
|
2843
|
+
let current = node;
|
|
2844
|
+
while (ts.isPropertyAccessExpression(current)) {
|
|
2845
|
+
segments.unshift(current.name.text);
|
|
2846
|
+
current = current.expression;
|
|
2847
|
+
}
|
|
2848
|
+
if (!ts.isIdentifier(current)) return void 0;
|
|
2849
|
+
segments.unshift(current.text);
|
|
2850
|
+
return { root: current, segments };
|
|
2851
|
+
}
|
|
2852
|
+
function isRuntimeIdentifierReference(ts, node) {
|
|
2853
|
+
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) return false;
|
|
2854
|
+
return ts.isExpressionNode(node) && !ts.isInTypeQuery(node) || ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node;
|
|
2855
|
+
}
|
|
2856
|
+
function hasLocalDeclaration(ts, checker, sourceFile, node) {
|
|
2857
|
+
const shorthand = ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node;
|
|
2858
|
+
const symbol = shorthand ? checker.getShorthandAssignmentValueSymbol(node.parent) : checker.getSymbolAtLocation(node);
|
|
2859
|
+
return Boolean(
|
|
2860
|
+
symbol?.declarations?.some((declaration) => declaration.getSourceFile() === sourceFile)
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2431
2863
|
function analyzeForbiddenGlobals(ts, source, filePath, layer, forbidden) {
|
|
2432
2864
|
const entries = new Set(forbidden);
|
|
2433
2865
|
if (entries.size === 0) return [];
|
|
2434
2866
|
const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
|
|
2867
|
+
const checker = singleFileTypeChecker(ts, sourceFile);
|
|
2435
2868
|
const violations = [];
|
|
2436
2869
|
const flag = (name, node) => violations.push(
|
|
2437
2870
|
violation("FORBIDDEN_GLOBAL", `${layer} must not use the ambient global "${name}".`, {
|
|
@@ -2443,12 +2876,24 @@ function analyzeForbiddenGlobals(ts, source, filePath, layer, forbidden) {
|
|
|
2443
2876
|
})
|
|
2444
2877
|
);
|
|
2445
2878
|
const visit = (node) => {
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2879
|
+
const nestedPropertyAccess = ts.isPropertyAccessExpression(node) && ts.isPropertyAccessExpression(node.parent) && node.parent.expression === node;
|
|
2880
|
+
if (ts.isPropertyAccessExpression(node) && !nestedPropertyAccess) {
|
|
2881
|
+
const path = propertyAccessPath(ts, node);
|
|
2882
|
+
if (path && !hasLocalDeclaration(ts, checker, sourceFile, path.root)) {
|
|
2883
|
+
const explicitGlobalThis = path.segments[0] === "globalThis";
|
|
2884
|
+
const normalized = explicitGlobalThis ? path.segments.slice(1) : path.segments;
|
|
2885
|
+
let match;
|
|
2886
|
+
for (let length = normalized.length; length >= (explicitGlobalThis ? 1 : 2); length -= 1) {
|
|
2887
|
+
const candidate = normalized.slice(0, length).join(".");
|
|
2888
|
+
if (entries.has(candidate)) {
|
|
2889
|
+
match = candidate;
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
if (match) flag(match, node);
|
|
2894
|
+
}
|
|
2895
|
+
} else if (ts.isIdentifier(node) && entries.has(node.text) && isRuntimeIdentifierReference(ts, node) && !hasLocalDeclaration(ts, checker, sourceFile, node)) {
|
|
2896
|
+
flag(node.text, node);
|
|
2452
2897
|
}
|
|
2453
2898
|
ts.forEachChild(node, visit);
|
|
2454
2899
|
};
|
|
@@ -2525,15 +2970,16 @@ function createAICodeGate(options = {}) {
|
|
|
2525
2970
|
const gateContext = context;
|
|
2526
2971
|
const filePath = gateContext?.filePath;
|
|
2527
2972
|
const contextLayer = gateContext?.layer;
|
|
2528
|
-
const
|
|
2973
|
+
const moduleSpecifiers2 = options.typescript ? extractModuleSpecifiersAst(options.typescript, source) : extractModuleSpecifiers(source);
|
|
2529
2974
|
const quotedStrings = options.typescript ? extractQuotedStringsAst(options.typescript, source) : extractQuotedStrings(source);
|
|
2530
2975
|
if (options.typescript && !options.allowNonLiteralDynamicImport?.(filePath)) {
|
|
2531
|
-
for (const
|
|
2976
|
+
for (const dependency of nonLiteralDynamicDependencies(options.typescript, source)) {
|
|
2977
|
+
const isRequire = dependency.kind === "require";
|
|
2532
2978
|
violations.push(
|
|
2533
2979
|
violation(
|
|
2534
|
-
"DYNAMIC_IMPORT_NOT_ALLOWLISTED",
|
|
2535
|
-
|
|
2536
|
-
{ line, filePath }
|
|
2980
|
+
isRequire ? "DYNAMIC_REQUIRE_NOT_ALLOWLISTED" : "DYNAMIC_IMPORT_NOT_ALLOWLISTED",
|
|
2981
|
+
`Non-literal ${isRequire ? "require call" : "dynamic import"} cannot be resolved statically; add the reviewed file to dynamicImportAllowlist.`,
|
|
2982
|
+
{ line: dependency.line, filePath }
|
|
2537
2983
|
)
|
|
2538
2984
|
);
|
|
2539
2985
|
}
|
|
@@ -2563,7 +3009,7 @@ function createAICodeGate(options = {}) {
|
|
|
2563
3009
|
);
|
|
2564
3010
|
}
|
|
2565
3011
|
}
|
|
2566
|
-
for (const specifier of
|
|
3012
|
+
for (const specifier of moduleSpecifiers2) {
|
|
2567
3013
|
const targetHit = options.resolveImportTarget?.(specifier.value, filePath) ?? (options.resolveImportLayer ? { layer: options.resolveImportLayer(specifier.value, filePath) } : void 0);
|
|
2568
3014
|
const sourceHit = typeof filePath === "string" ? options.resolveImportTarget?.(filePath) ?? (options.resolveImportLayer ? { layer: contextLayer, relPath: void 0 } : void 0) : void 0;
|
|
2569
3015
|
const targetLayer = targetHit?.layer;
|
|
@@ -2762,6 +3208,214 @@ function createAICodeGate(options = {}) {
|
|
|
2762
3208
|
};
|
|
2763
3209
|
}
|
|
2764
3210
|
|
|
3211
|
+
// src/domain/analysis.ts
|
|
3212
|
+
var ANALYSIS_IR_SCHEMA_VERSION = "1.0";
|
|
3213
|
+
function deterministicHash(value) {
|
|
3214
|
+
let hash = 2166136261;
|
|
3215
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
3216
|
+
hash ^= value.charCodeAt(index);
|
|
3217
|
+
hash = Math.imul(hash, 16777619);
|
|
3218
|
+
}
|
|
3219
|
+
return `fnv1a-${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
3220
|
+
}
|
|
3221
|
+
function stableSerialize(value) {
|
|
3222
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3223
|
+
if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
|
|
3224
|
+
const object = value;
|
|
3225
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableSerialize(object[key])}`).join(",")}}`;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
// src/kernel/analysis.ts
|
|
3229
|
+
function loadContract(input, source) {
|
|
3230
|
+
const loaded = typeof input === "string" ? parseArkConfigJson(input, source) : loadArkConfigContract(input, source);
|
|
3231
|
+
return { ...loaded, policyHash: deterministicHash(stableSerialize(loaded.config)) };
|
|
3232
|
+
}
|
|
3233
|
+
function normalizePath(value) {
|
|
3234
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3235
|
+
}
|
|
3236
|
+
function isIdentifierCharacter(value) {
|
|
3237
|
+
return value !== void 0 && /[A-Za-z0-9_$]/.test(value);
|
|
3238
|
+
}
|
|
3239
|
+
function skipWhitespace(source, index) {
|
|
3240
|
+
while (index < source.length && /\s/.test(source[index])) index += 1;
|
|
3241
|
+
return index;
|
|
3242
|
+
}
|
|
3243
|
+
function readString(source, index) {
|
|
3244
|
+
const quote = source[index];
|
|
3245
|
+
if (quote !== "'" && quote !== '"') return void 0;
|
|
3246
|
+
const start = index;
|
|
3247
|
+
let value = "";
|
|
3248
|
+
for (index += 1; index < source.length; index += 1) {
|
|
3249
|
+
const current = source[index];
|
|
3250
|
+
if (current === quote) {
|
|
3251
|
+
return { value, offset: start, excerpt: source.slice(start, index + 1) };
|
|
3252
|
+
}
|
|
3253
|
+
if (current === "\\" && index + 1 < source.length) {
|
|
3254
|
+
value += source[index + 1];
|
|
3255
|
+
index += 1;
|
|
3256
|
+
} else {
|
|
3257
|
+
value += current;
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
return void 0;
|
|
3261
|
+
}
|
|
3262
|
+
function isWordAt(source, word, index) {
|
|
3263
|
+
return source.startsWith(word, index) && !isIdentifierCharacter(source[index - 1]) && !isIdentifierCharacter(source[index + word.length]);
|
|
3264
|
+
}
|
|
3265
|
+
function specifierAfterImport(source, index) {
|
|
3266
|
+
index = skipWhitespace(source, index + "import".length);
|
|
3267
|
+
if (source[index] === "(") return readString(source, skipWhitespace(source, index + 1));
|
|
3268
|
+
return specifierInStaticStatement(source, index, true);
|
|
3269
|
+
}
|
|
3270
|
+
function specifierAfterExport(source, index) {
|
|
3271
|
+
return specifierInStaticStatement(source, index + "export".length, false);
|
|
3272
|
+
}
|
|
3273
|
+
function specifierInStaticStatement(source, index, allowDirectSpecifier) {
|
|
3274
|
+
for (; index < source.length; index += 1) {
|
|
3275
|
+
if (source[index] === ";") return void 0;
|
|
3276
|
+
if (isWordAt(source, "from", index)) {
|
|
3277
|
+
return readString(source, skipWhitespace(source, index + "from".length));
|
|
3278
|
+
}
|
|
3279
|
+
if (allowDirectSpecifier && (source[index] === "'" || source[index] === '"')) {
|
|
3280
|
+
return readString(source, index);
|
|
3281
|
+
}
|
|
3282
|
+
if (index > 0 && (isWordAt(source, "import", index) || isWordAt(source, "export", index))) {
|
|
3283
|
+
return void 0;
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
return void 0;
|
|
3287
|
+
}
|
|
3288
|
+
function moduleSpecifiers(source) {
|
|
3289
|
+
const result = [];
|
|
3290
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
3291
|
+
const current = source[index];
|
|
3292
|
+
if (current === "/" && source[index + 1] === "/") {
|
|
3293
|
+
index = source.indexOf("\n", index + 2);
|
|
3294
|
+
if (index < 0) break;
|
|
3295
|
+
continue;
|
|
3296
|
+
}
|
|
3297
|
+
if (current === "/" && source[index + 1] === "*") {
|
|
3298
|
+
const end = source.indexOf("*/", index + 2);
|
|
3299
|
+
if (end < 0) break;
|
|
3300
|
+
index = end + 1;
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
if (current === "'" || current === '"' || current === "`") {
|
|
3304
|
+
const string = readString(source, index);
|
|
3305
|
+
if (string) index = string.offset + string.excerpt.length - 1;
|
|
3306
|
+
continue;
|
|
3307
|
+
}
|
|
3308
|
+
const specifier = isWordAt(source, "import", index) ? specifierAfterImport(source, index) : isWordAt(source, "export", index) ? specifierAfterExport(source, index) : void 0;
|
|
3309
|
+
if (specifier) result.push(specifier);
|
|
3310
|
+
}
|
|
3311
|
+
return result;
|
|
3312
|
+
}
|
|
3313
|
+
function importEdges(file, files) {
|
|
3314
|
+
const edges = [];
|
|
3315
|
+
for (const moduleSpecifier of moduleSpecifiers(file.content)) {
|
|
3316
|
+
const specifier = moduleSpecifier.value;
|
|
3317
|
+
if (!specifier.startsWith(".")) continue;
|
|
3318
|
+
const line = file.content.slice(0, moduleSpecifier.offset).split("\n").length;
|
|
3319
|
+
const evidence = {
|
|
3320
|
+
kind: "import",
|
|
3321
|
+
file: file.path,
|
|
3322
|
+
line,
|
|
3323
|
+
excerpt: moduleSpecifier.excerpt
|
|
3324
|
+
};
|
|
3325
|
+
const target = resolveSpecifier(file.path, specifier, files);
|
|
3326
|
+
edges.push({
|
|
3327
|
+
from: file.path,
|
|
3328
|
+
specifier,
|
|
3329
|
+
to: target?.path ?? null,
|
|
3330
|
+
resolution: target ? "resolved" : "unresolved",
|
|
3331
|
+
fromLayer: file.layer,
|
|
3332
|
+
toLayer: target?.layer ?? null,
|
|
3333
|
+
evidence
|
|
3334
|
+
});
|
|
3335
|
+
}
|
|
3336
|
+
return edges;
|
|
3337
|
+
}
|
|
3338
|
+
function resolveSpecifier(from, specifier, files) {
|
|
3339
|
+
const segments = from.split("/");
|
|
3340
|
+
segments.pop();
|
|
3341
|
+
for (const segment of specifier.split("/")) {
|
|
3342
|
+
if (segment === "." || segment === "") continue;
|
|
3343
|
+
if (segment === "..") segments.pop();
|
|
3344
|
+
else segments.push(segment);
|
|
3345
|
+
}
|
|
3346
|
+
const base = segments.join("/");
|
|
3347
|
+
for (const candidate of [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, `${base}.cts`, `${base}/index.ts`, `${base}/index.tsx`]) {
|
|
3348
|
+
const found = files.get(candidate);
|
|
3349
|
+
if (found) return found;
|
|
3350
|
+
}
|
|
3351
|
+
return void 0;
|
|
3352
|
+
}
|
|
3353
|
+
function violationsFor(edges, config) {
|
|
3354
|
+
const violations = [];
|
|
3355
|
+
for (const edge of edges) {
|
|
3356
|
+
if (!edge.to || !edge.fromLayer || !edge.toLayer) continue;
|
|
3357
|
+
const rule = findDeniedEdgeRule(config.rules, edge.fromLayer, edge.toLayer, {
|
|
3358
|
+
fromPath: edge.from,
|
|
3359
|
+
toPath: edge.to,
|
|
3360
|
+
layers: config.layers
|
|
3361
|
+
});
|
|
3362
|
+
if (!rule) continue;
|
|
3363
|
+
violations.push({
|
|
3364
|
+
ruleId: `layer-dependency:${rule.from}->${rule.to}`,
|
|
3365
|
+
message: rule.message ?? `${rule.from} must not depend on ${rule.to}.`,
|
|
3366
|
+
edge,
|
|
3367
|
+
evidence: edge.evidence
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
return violations;
|
|
3371
|
+
}
|
|
3372
|
+
function analyzeProject(input) {
|
|
3373
|
+
const files = input.files.map((inputFile) => {
|
|
3374
|
+
const path = normalizePath(inputFile.path);
|
|
3375
|
+
return {
|
|
3376
|
+
path,
|
|
3377
|
+
content: inputFile.content,
|
|
3378
|
+
contentHash: deterministicHash(inputFile.content),
|
|
3379
|
+
layer: layerForRelativePath(path, input.contract.config.layers) ?? null
|
|
3380
|
+
};
|
|
3381
|
+
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
3382
|
+
const fileByPath = new Map(files.map((file) => [file.path, file]));
|
|
3383
|
+
const edges = files.flatMap((file) => importEdges(file, fileByPath));
|
|
3384
|
+
const capabilityUses = [];
|
|
3385
|
+
const violations = violationsFor(edges, input.contract.config);
|
|
3386
|
+
return {
|
|
3387
|
+
ir: {
|
|
3388
|
+
schemaVersion: ANALYSIS_IR_SCHEMA_VERSION,
|
|
3389
|
+
policyHash: input.contract.policyHash,
|
|
3390
|
+
compilerOptionsHash: deterministicHash(stableSerialize(input.compilerOptions ?? {})),
|
|
3391
|
+
files,
|
|
3392
|
+
layers: input.contract.config.layers.map((layer) => layer.name),
|
|
3393
|
+
edges,
|
|
3394
|
+
capabilityUses,
|
|
3395
|
+
violations
|
|
3396
|
+
}
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
function analyzeChange(input) {
|
|
3400
|
+
const files = new Map(input.files.map((file) => [normalizePath(file.path), file]));
|
|
3401
|
+
for (const change of input.changes) {
|
|
3402
|
+
const path = normalizePath(change.path);
|
|
3403
|
+
if ("delete" in change && change.delete) files.delete(path);
|
|
3404
|
+
else if ("content" in change) files.set(path, { path, content: change.content });
|
|
3405
|
+
}
|
|
3406
|
+
return analyzeProject({
|
|
3407
|
+
contract: input.contract,
|
|
3408
|
+
files: [...files.values()],
|
|
3409
|
+
compilerOptions: input.compilerOptions
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
function explainViolation(violation2) {
|
|
3413
|
+
const location = `${violation2.evidence.file}:${violation2.evidence.line}`;
|
|
3414
|
+
if (!violation2.edge) return `${violation2.ruleId} at ${location}: ${violation2.message}`;
|
|
3415
|
+
const target = violation2.edge.to ?? violation2.edge.specifier;
|
|
3416
|
+
return `${violation2.ruleId} at ${location}: ${violation2.edge.from} imports ${target}. ${violation2.message}`;
|
|
3417
|
+
}
|
|
3418
|
+
|
|
2765
3419
|
// src/kernel/projections/ProjectionRegistry.ts
|
|
2766
3420
|
var InMemoryReadModelStore = class {
|
|
2767
3421
|
states = /* @__PURE__ */ new Map();
|
|
@@ -3070,22 +3724,13 @@ var WorkflowEngineImpl = class {
|
|
|
3070
3724
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3071
3725
|
snapshot.attempts[step.name] = attempt;
|
|
3072
3726
|
await this.store.save(snapshot);
|
|
3727
|
+
let result;
|
|
3073
3728
|
try {
|
|
3074
|
-
|
|
3729
|
+
result = await withTimeout(
|
|
3075
3730
|
(signal) => Promise.resolve(step.execute(snapshot.context, this.bus, signal)),
|
|
3076
3731
|
step.timeoutMs,
|
|
3077
3732
|
step.name
|
|
3078
3733
|
);
|
|
3079
|
-
if (result) Object.assign(snapshot.context, result);
|
|
3080
|
-
snapshot.completedSteps.push(step.name);
|
|
3081
|
-
snapshot.currentStep = void 0;
|
|
3082
|
-
snapshot.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3083
|
-
await this.store.save(snapshot);
|
|
3084
|
-
await this.audit("workflow.step.completed", snapshot, {
|
|
3085
|
-
step: step.name,
|
|
3086
|
-
attempt
|
|
3087
|
-
});
|
|
3088
|
-
return;
|
|
3089
3734
|
} catch (err) {
|
|
3090
3735
|
if (attempt < maxAttempts) {
|
|
3091
3736
|
if (retry.delayMs) await sleep(retry.delayMs);
|
|
@@ -3101,6 +3746,16 @@ var WorkflowEngineImpl = class {
|
|
|
3101
3746
|
});
|
|
3102
3747
|
throw err;
|
|
3103
3748
|
}
|
|
3749
|
+
if (result) Object.assign(snapshot.context, result);
|
|
3750
|
+
snapshot.completedSteps.push(step.name);
|
|
3751
|
+
snapshot.currentStep = void 0;
|
|
3752
|
+
snapshot.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3753
|
+
await this.store.save(snapshot);
|
|
3754
|
+
await this.audit("workflow.step.completed", snapshot, {
|
|
3755
|
+
step: step.name,
|
|
3756
|
+
attempt
|
|
3757
|
+
});
|
|
3758
|
+
return;
|
|
3104
3759
|
}
|
|
3105
3760
|
throw new Error(`Workflow step "${step.name}" did not complete.`);
|
|
3106
3761
|
}
|
|
@@ -3292,6 +3947,7 @@ function createLenientArkKernel(options = {}) {
|
|
|
3292
3947
|
}
|
|
3293
3948
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3294
3949
|
0 && (module.exports = {
|
|
3950
|
+
ANALYSIS_IR_SCHEMA_VERSION,
|
|
3295
3951
|
DEFAULT_MAX_HISTORY_SIZE,
|
|
3296
3952
|
EventContractRegistryImpl,
|
|
3297
3953
|
EventContractViolationError,
|
|
@@ -3309,6 +3965,8 @@ function createLenientArkKernel(options = {}) {
|
|
|
3309
3965
|
SourceMetadataOverrideError,
|
|
3310
3966
|
UnknownEventSourceError,
|
|
3311
3967
|
UnregisteredIntentError,
|
|
3968
|
+
analyzeChange,
|
|
3969
|
+
analyzeProject,
|
|
3312
3970
|
architecturalPolicies,
|
|
3313
3971
|
buildPublishPolicyContext,
|
|
3314
3972
|
checkAdapterGovernance,
|
|
@@ -3343,8 +4001,12 @@ function createLenientArkKernel(options = {}) {
|
|
|
3343
4001
|
definePolicy,
|
|
3344
4002
|
definePort,
|
|
3345
4003
|
definePublishPolicy,
|
|
4004
|
+
deterministicHash,
|
|
3346
4005
|
elevenLayerProfile,
|
|
4006
|
+
explainViolation,
|
|
3347
4007
|
isLayerPolicy,
|
|
4008
|
+
loadContract,
|
|
4009
|
+
stableSerialize,
|
|
3348
4010
|
syncRegistryToGraph,
|
|
3349
4011
|
validateIntentName,
|
|
3350
4012
|
version
|