arkgate 2.11.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 +147 -0
- package/README.md +70 -41
- 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 +97 -29
- package/bin/lib/agent-gates.mjs +79 -2093
- package/bin/lib/architecture-scan.mjs +8 -0
- package/bin/lib/ci-and-commands.mjs +392 -0
- package/bin/lib/codex-home.mjs +7 -0
- package/bin/lib/config-contract.mjs +331 -0
- package/bin/lib/deploy-path.mjs +205 -0
- package/bin/lib/doctor-plan.mjs +43 -16
- package/bin/lib/enforcement-profiles.mjs +97 -0
- package/bin/lib/gate-files.mjs +223 -0
- package/bin/lib/hook-templates.mjs +99 -0
- package/bin/lib/host-support-matrix.mjs +77 -0
- package/bin/lib/install-migrate.mjs +473 -0
- package/bin/lib/mcp-adoption.mjs +455 -0
- package/bin/lib/open-html.mjs +75 -0
- package/bin/lib/presets.mjs +6 -2
- package/bin/lib/safety-diagnostics.mjs +31 -11
- package/bin/lib/skill-install.mjs +323 -0
- package/bin/lib/ts-resolve.mjs +2 -1
- package/bin/lib/typescript-host.mjs +88 -0
- package/bin/lib/weakest-link.mjs +417 -0
- package/bin/lib/write-path-capabilities.mjs +182 -0
- package/bin/lib/write-path-detect.mjs +101 -0
- 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 +43 -21
- 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 +22 -10
- 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-autopilot.md +77 -45
- package/templates/skills/ark-coverage.md +2 -2
- package/templates/skills/ark-explain.md +2 -1
- package/templates/skills/ark-explore.md +135 -34
- 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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var version = "2.
|
|
2
|
+
var version = "2.13.0";
|
|
3
3
|
|
|
4
4
|
// src/kernel/intent/IntentRegistry.ts
|
|
5
5
|
var IntentRegistry = class {
|
|
@@ -663,6 +663,10 @@ async function recordInterceptorError(interceptor, event, error, deps) {
|
|
|
663
663
|
}
|
|
664
664
|
|
|
665
665
|
// src/domain/layerMatch.ts
|
|
666
|
+
var regexpCache = /* @__PURE__ */ new Map();
|
|
667
|
+
function escapeLiteral(ch) {
|
|
668
|
+
return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
669
|
+
}
|
|
666
670
|
function normalizeGlobSeparators(pattern) {
|
|
667
671
|
let out = "";
|
|
668
672
|
for (let i = 0; i < pattern.length; i += 1) {
|
|
@@ -681,6 +685,91 @@ function normalizeGlobSeparators(pattern) {
|
|
|
681
685
|
}
|
|
682
686
|
return out;
|
|
683
687
|
}
|
|
688
|
+
function bracesBalanced(glob) {
|
|
689
|
+
let depth = 0;
|
|
690
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
691
|
+
const c = glob[i];
|
|
692
|
+
if (c === "\\") {
|
|
693
|
+
i += 1;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (c === "{") depth += 1;
|
|
697
|
+
else if (c === "}") {
|
|
698
|
+
depth -= 1;
|
|
699
|
+
if (depth < 0) return false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return depth === 0;
|
|
703
|
+
}
|
|
704
|
+
function globToRegExp(pattern) {
|
|
705
|
+
const cached = regexpCache.get(pattern);
|
|
706
|
+
if (cached) return cached;
|
|
707
|
+
const glob = normalizeGlobSeparators(pattern);
|
|
708
|
+
const useBraces = bracesBalanced(glob);
|
|
709
|
+
let out = "";
|
|
710
|
+
let braceDepth = 0;
|
|
711
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
712
|
+
const c = glob[i];
|
|
713
|
+
if (c === "\\" && i + 1 < glob.length) {
|
|
714
|
+
out += escapeLiteral(glob[i + 1]);
|
|
715
|
+
i += 1;
|
|
716
|
+
} else if (c === "*") {
|
|
717
|
+
if (glob[i + 1] === "*") {
|
|
718
|
+
if (glob[i + 2] === "/") {
|
|
719
|
+
out += "(?:.*/)?";
|
|
720
|
+
i += 2;
|
|
721
|
+
} else {
|
|
722
|
+
out += ".*";
|
|
723
|
+
i += 1;
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
out += "[^/]*";
|
|
727
|
+
}
|
|
728
|
+
} else if (c === "?") {
|
|
729
|
+
out += "[^/]";
|
|
730
|
+
} else if (c === "{" && useBraces) {
|
|
731
|
+
out += "(?:";
|
|
732
|
+
braceDepth += 1;
|
|
733
|
+
} else if (c === "}" && useBraces && braceDepth > 0) {
|
|
734
|
+
out += ")";
|
|
735
|
+
braceDepth -= 1;
|
|
736
|
+
} else if (c === "," && useBraces && braceDepth > 0) {
|
|
737
|
+
out += "|";
|
|
738
|
+
} else {
|
|
739
|
+
out += escapeLiteral(c);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const re = new RegExp(`^${out}$`);
|
|
743
|
+
regexpCache.set(pattern, re);
|
|
744
|
+
return re;
|
|
745
|
+
}
|
|
746
|
+
function patternSpecificity(pattern) {
|
|
747
|
+
const glob = normalizeGlobSeparators(String(pattern));
|
|
748
|
+
const beforeWildcard = glob.split("*")[0];
|
|
749
|
+
const literalSegments = beforeWildcard.split("/").filter(Boolean).length;
|
|
750
|
+
const literalLength = glob.replace(/\*/g, "").length;
|
|
751
|
+
return literalSegments * 1e4 + literalLength;
|
|
752
|
+
}
|
|
753
|
+
function layerForRelativePath(relPath, layers) {
|
|
754
|
+
const rel = String(relPath).split(/[/\\]/).join("/");
|
|
755
|
+
let bestName;
|
|
756
|
+
let bestScore = -1;
|
|
757
|
+
for (const layer of layers ?? []) {
|
|
758
|
+
if ((layer.exclude ?? []).some((pattern) => globToRegExp(pattern).test(rel))) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
for (const pattern of layer.patterns ?? []) {
|
|
762
|
+
if (globToRegExp(pattern).test(rel)) {
|
|
763
|
+
const score = patternSpecificity(pattern);
|
|
764
|
+
if (score > bestScore) {
|
|
765
|
+
bestScore = score;
|
|
766
|
+
bestName = layer.name;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return bestName;
|
|
772
|
+
}
|
|
684
773
|
function sliceIdForPath(relPath, sliceFolders) {
|
|
685
774
|
if (!sliceFolders?.length) return void 0;
|
|
686
775
|
const parts = String(relPath).split(/[/\\]/).filter(Boolean);
|
|
@@ -1275,13 +1364,13 @@ function formatPath(parent, field) {
|
|
|
1275
1364
|
return parent ? `${parent}.${field}` : field;
|
|
1276
1365
|
}
|
|
1277
1366
|
function validateSchemaField(value, fieldSchema, path, contract, issues) {
|
|
1278
|
-
const
|
|
1279
|
-
if (fieldSchema.type !== "unknown" &&
|
|
1367
|
+
const valueType2 = actualType(value);
|
|
1368
|
+
if (fieldSchema.type !== "unknown" && valueType2 !== fieldSchema.type) {
|
|
1280
1369
|
issues.push({
|
|
1281
1370
|
intent: contract.intent,
|
|
1282
1371
|
version: contract.version,
|
|
1283
1372
|
field: path,
|
|
1284
|
-
message: `Expected ${fieldSchema.type}, received ${
|
|
1373
|
+
message: `Expected ${fieldSchema.type}, received ${valueType2}.`
|
|
1285
1374
|
});
|
|
1286
1375
|
return;
|
|
1287
1376
|
}
|
|
@@ -1838,6 +1927,321 @@ function syncRegistryToGraph(registry, graph, options = {}) {
|
|
|
1838
1927
|
}
|
|
1839
1928
|
}
|
|
1840
1929
|
|
|
1930
|
+
// src/domain/configContract.ts
|
|
1931
|
+
var ARK_CONFIG_SCHEMA_VERSION = "1.0";
|
|
1932
|
+
var ARK_CONFIG_SCHEMA_URL = "https://unpkg.com/arkgate@2/schemas/ark.config.schema.json";
|
|
1933
|
+
var DEFAULT_LAYER_NAMES = [
|
|
1934
|
+
"DomainModel",
|
|
1935
|
+
"ApplicationOrchestration",
|
|
1936
|
+
"PersistenceAdapters",
|
|
1937
|
+
"IntegrationAdapters",
|
|
1938
|
+
"WorkflowSagaEngine",
|
|
1939
|
+
"BackgroundJobsScheduling",
|
|
1940
|
+
"PresentationAdapters",
|
|
1941
|
+
"ReportingReadModels",
|
|
1942
|
+
"ExtensibilityMetadata",
|
|
1943
|
+
"SecurityAuditObservability",
|
|
1944
|
+
"Kernel"
|
|
1945
|
+
];
|
|
1946
|
+
var DEFAULT_ALLOWED_FLOWS = /* @__PURE__ */ new Set([
|
|
1947
|
+
"PresentationAdapters->ApplicationOrchestration",
|
|
1948
|
+
"ApplicationOrchestration->DomainModel",
|
|
1949
|
+
"WorkflowSagaEngine->ApplicationOrchestration",
|
|
1950
|
+
"WorkflowSagaEngine->DomainModel",
|
|
1951
|
+
"BackgroundJobsScheduling->ApplicationOrchestration"
|
|
1952
|
+
]);
|
|
1953
|
+
function createDefaultRules() {
|
|
1954
|
+
const rules = [];
|
|
1955
|
+
for (const from of DEFAULT_LAYER_NAMES) {
|
|
1956
|
+
for (const to of DEFAULT_LAYER_NAMES) {
|
|
1957
|
+
if (from === to || DEFAULT_ALLOWED_FLOWS.has(`${from}->${to}`)) continue;
|
|
1958
|
+
rules.push({ from, to, allowed: false });
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
return rules;
|
|
1962
|
+
}
|
|
1963
|
+
var DEFAULT_ARK_CONFIG_RULES = createDefaultRules();
|
|
1964
|
+
var stringArraySchema = {
|
|
1965
|
+
type: "array",
|
|
1966
|
+
items: { type: "string", minLength: 1 },
|
|
1967
|
+
uniqueItems: true
|
|
1968
|
+
};
|
|
1969
|
+
var ARK_CONFIG_SCHEMA = {
|
|
1970
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1971
|
+
$id: ARK_CONFIG_SCHEMA_URL,
|
|
1972
|
+
title: "ArkGate architecture contract",
|
|
1973
|
+
description: "Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",
|
|
1974
|
+
type: "object",
|
|
1975
|
+
additionalProperties: false,
|
|
1976
|
+
required: ["$schema", "schemaVersion", "include", "layers", "rules"],
|
|
1977
|
+
properties: {
|
|
1978
|
+
$schema: {
|
|
1979
|
+
type: "string",
|
|
1980
|
+
minLength: 1,
|
|
1981
|
+
default: ARK_CONFIG_SCHEMA_URL,
|
|
1982
|
+
description: "Editor-facing URL or local path for this JSON Schema."
|
|
1983
|
+
},
|
|
1984
|
+
schemaVersion: {
|
|
1985
|
+
type: "string",
|
|
1986
|
+
const: ARK_CONFIG_SCHEMA_VERSION,
|
|
1987
|
+
default: ARK_CONFIG_SCHEMA_VERSION
|
|
1988
|
+
},
|
|
1989
|
+
name: { type: "string", minLength: 1 },
|
|
1990
|
+
include: { ...stringArraySchema, minItems: 1, default: ["src"] },
|
|
1991
|
+
exclude: { ...stringArraySchema, default: [] },
|
|
1992
|
+
excludeGenerated: { type: "boolean", default: true },
|
|
1993
|
+
frameworkOverlay: { type: "string", minLength: 1 },
|
|
1994
|
+
layers: {
|
|
1995
|
+
type: "array",
|
|
1996
|
+
default: [],
|
|
1997
|
+
items: { $ref: "#/$defs/layer" }
|
|
1998
|
+
},
|
|
1999
|
+
rules: {
|
|
2000
|
+
type: "array",
|
|
2001
|
+
default: DEFAULT_ARK_CONFIG_RULES,
|
|
2002
|
+
items: { $ref: "#/$defs/rule" }
|
|
2003
|
+
},
|
|
2004
|
+
cyclePolicy: {
|
|
2005
|
+
type: "string",
|
|
2006
|
+
enum: ["strict", "soft", "framework-soft", "off"],
|
|
2007
|
+
default: "strict"
|
|
2008
|
+
},
|
|
2009
|
+
dynamicImportAllowlist: { ...stringArraySchema, default: [] },
|
|
2010
|
+
safety: {
|
|
2011
|
+
$ref: "#/$defs/safety",
|
|
2012
|
+
default: {
|
|
2013
|
+
maxTsSuppressions: 0,
|
|
2014
|
+
maxAnyCasts: 0,
|
|
2015
|
+
allowInMemory: false,
|
|
2016
|
+
allowDisabledPeerIsolation: false
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
},
|
|
2020
|
+
$defs: {
|
|
2021
|
+
layer: {
|
|
2022
|
+
type: "object",
|
|
2023
|
+
additionalProperties: false,
|
|
2024
|
+
required: ["name", "patterns"],
|
|
2025
|
+
properties: {
|
|
2026
|
+
name: { type: "string", minLength: 1 },
|
|
2027
|
+
patterns: { ...stringArraySchema, minItems: 1 },
|
|
2028
|
+
exclude: stringArraySchema,
|
|
2029
|
+
intentPrefixes: stringArraySchema,
|
|
2030
|
+
description: { type: "string", minLength: 1 },
|
|
2031
|
+
forbiddenGlobals: stringArraySchema,
|
|
2032
|
+
mayImportInfrastructure: { type: "boolean" },
|
|
2033
|
+
optional: { type: "boolean" }
|
|
2034
|
+
}
|
|
2035
|
+
},
|
|
2036
|
+
rule: {
|
|
2037
|
+
type: "object",
|
|
2038
|
+
additionalProperties: false,
|
|
2039
|
+
required: ["from", "to", "allowed"],
|
|
2040
|
+
properties: {
|
|
2041
|
+
from: { type: "string", minLength: 1 },
|
|
2042
|
+
to: { type: "string", minLength: 1 },
|
|
2043
|
+
allowed: { type: "boolean" },
|
|
2044
|
+
message: { type: "string", minLength: 1 },
|
|
2045
|
+
peerIsolation: { type: "boolean" },
|
|
2046
|
+
sliceFolders: { ...stringArraySchema, minItems: 1 }
|
|
2047
|
+
}
|
|
2048
|
+
},
|
|
2049
|
+
safety: {
|
|
2050
|
+
type: "object",
|
|
2051
|
+
additionalProperties: false,
|
|
2052
|
+
properties: {
|
|
2053
|
+
maxTsSuppressions: { type: "integer", minimum: 0, default: 0 },
|
|
2054
|
+
maxAnyCasts: { type: "integer", minimum: 0, default: 0 },
|
|
2055
|
+
allowInMemory: { type: "boolean", default: false },
|
|
2056
|
+
allowDisabledPeerIsolation: { type: "boolean", default: false }
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
var ArkConfigValidationError = class extends Error {
|
|
2062
|
+
issues;
|
|
2063
|
+
source;
|
|
2064
|
+
constructor(source, issues) {
|
|
2065
|
+
super(
|
|
2066
|
+
`Invalid ArkGate config (${source}):
|
|
2067
|
+
${issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n")}`
|
|
2068
|
+
);
|
|
2069
|
+
this.name = "ArkConfigValidationError";
|
|
2070
|
+
this.source = source;
|
|
2071
|
+
this.issues = issues;
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
function isObject(value) {
|
|
2075
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2076
|
+
}
|
|
2077
|
+
function propertyPath(parent, key) {
|
|
2078
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
|
|
2079
|
+
}
|
|
2080
|
+
function valueType(value) {
|
|
2081
|
+
if (value === null) return "null";
|
|
2082
|
+
if (Array.isArray(value)) return "array";
|
|
2083
|
+
return typeof value;
|
|
2084
|
+
}
|
|
2085
|
+
function resolveSchemaRef(ref, root) {
|
|
2086
|
+
const prefix = "#/$defs/";
|
|
2087
|
+
if (!ref.startsWith(prefix)) return void 0;
|
|
2088
|
+
return root.$defs[ref.slice(prefix.length)];
|
|
2089
|
+
}
|
|
2090
|
+
function validateNode(value, schema, path, root, issues) {
|
|
2091
|
+
if (schema.$ref) {
|
|
2092
|
+
const referenced = resolveSchemaRef(schema.$ref, root);
|
|
2093
|
+
if (!referenced) {
|
|
2094
|
+
issues.push({ path, message: `schema reference ${schema.$ref} cannot be resolved` });
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
validateNode(value, referenced, path, root, issues);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (schema.const !== void 0 && !Object.is(value, schema.const)) {
|
|
2101
|
+
issues.push({ path, message: `must equal ${JSON.stringify(schema.const)}` });
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
|
|
2105
|
+
issues.push({ path, message: `must be one of ${schema.enum.map(String).join(", ")}` });
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (schema.type === "object") {
|
|
2109
|
+
if (!isObject(value)) {
|
|
2110
|
+
issues.push({ path, message: `must be an object; received ${valueType(value)}` });
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
const properties = schema.properties ?? {};
|
|
2114
|
+
for (const key of schema.required ?? []) {
|
|
2115
|
+
if (value[key] === void 0) {
|
|
2116
|
+
issues.push({ path: propertyPath(path, key), message: "is required" });
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
if (schema.additionalProperties === false) {
|
|
2120
|
+
for (const key of Object.keys(value)) {
|
|
2121
|
+
if (!(key in properties)) {
|
|
2122
|
+
issues.push({ path: propertyPath(path, key), message: "unknown field" });
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
for (const [key, childSchema] of Object.entries(properties)) {
|
|
2127
|
+
if (value[key] !== void 0) {
|
|
2128
|
+
validateNode(value[key], childSchema, propertyPath(path, key), root, issues);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
if (schema.type === "array") {
|
|
2134
|
+
if (!Array.isArray(value)) {
|
|
2135
|
+
issues.push({ path, message: `must be an array; received ${valueType(value)}` });
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
2139
|
+
issues.push({ path, message: `must contain at least ${schema.minItems} item(s)` });
|
|
2140
|
+
}
|
|
2141
|
+
if (schema.uniqueItems) {
|
|
2142
|
+
const serialized = value.map((entry) => JSON.stringify(entry));
|
|
2143
|
+
if (new Set(serialized).size !== serialized.length) {
|
|
2144
|
+
issues.push({ path, message: "must not contain duplicate items" });
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
if (schema.items) {
|
|
2148
|
+
value.forEach(
|
|
2149
|
+
(entry, index) => validateNode(entry, schema.items, `${path}[${index}]`, root, issues)
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
if (schema.type === "string") {
|
|
2155
|
+
if (typeof value !== "string") {
|
|
2156
|
+
issues.push({ path, message: `must be a string; received ${valueType(value)}` });
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
2160
|
+
issues.push({ path, message: `must contain at least ${schema.minLength} character(s)` });
|
|
2161
|
+
}
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
if (schema.type === "boolean") {
|
|
2165
|
+
if (typeof value !== "boolean") {
|
|
2166
|
+
issues.push({ path, message: `must be a boolean; received ${valueType(value)}` });
|
|
2167
|
+
}
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
if (schema.type === "integer") {
|
|
2171
|
+
if (!Number.isInteger(value)) {
|
|
2172
|
+
issues.push({ path, message: `must be an integer; received ${valueType(value)}` });
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
2176
|
+
issues.push({ path, message: `must be at least ${schema.minimum}` });
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
function defaultedConfig(input) {
|
|
2181
|
+
return {
|
|
2182
|
+
...input,
|
|
2183
|
+
$schema: input.$schema === void 0 ? ARK_CONFIG_SCHEMA_URL : input.$schema,
|
|
2184
|
+
schemaVersion: input.schemaVersion === void 0 ? ARK_CONFIG_SCHEMA_VERSION : input.schemaVersion,
|
|
2185
|
+
include: input.include === void 0 ? ["src"] : input.include,
|
|
2186
|
+
layers: input.layers === void 0 ? [] : input.layers,
|
|
2187
|
+
rules: input.rules === void 0 ? DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule })) : input.rules
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
function migrateArkConfig(input, source = "ark.config.json") {
|
|
2191
|
+
if (!isObject(input)) {
|
|
2192
|
+
throw new ArkConfigValidationError(source, [
|
|
2193
|
+
{ path: "$", message: `must be an object; received ${valueType(input)}` }
|
|
2194
|
+
]);
|
|
2195
|
+
}
|
|
2196
|
+
const migratedFrom = input.schemaVersion === void 0 ? "unversioned" : null;
|
|
2197
|
+
if (input.schemaVersion !== void 0 && input.schemaVersion !== ARK_CONFIG_SCHEMA_VERSION) {
|
|
2198
|
+
throw new ArkConfigValidationError(source, [
|
|
2199
|
+
{
|
|
2200
|
+
path: "$.schemaVersion",
|
|
2201
|
+
message: `unsupported version ${JSON.stringify(input.schemaVersion)}; expected ${ARK_CONFIG_SCHEMA_VERSION}`
|
|
2202
|
+
}
|
|
2203
|
+
]);
|
|
2204
|
+
}
|
|
2205
|
+
return { candidate: defaultedConfig(input), migratedFrom };
|
|
2206
|
+
}
|
|
2207
|
+
function loadArkConfigContract(input, source = "ark.config.json") {
|
|
2208
|
+
const { candidate, migratedFrom } = migrateArkConfig(input, source);
|
|
2209
|
+
const issues = [];
|
|
2210
|
+
validateNode(
|
|
2211
|
+
candidate,
|
|
2212
|
+
ARK_CONFIG_SCHEMA,
|
|
2213
|
+
"$",
|
|
2214
|
+
ARK_CONFIG_SCHEMA,
|
|
2215
|
+
issues
|
|
2216
|
+
);
|
|
2217
|
+
if (issues.length > 0) throw new ArkConfigValidationError(source, issues);
|
|
2218
|
+
return { config: candidate, migratedFrom };
|
|
2219
|
+
}
|
|
2220
|
+
function parseArkConfigJson(json, source = "ark.config.json") {
|
|
2221
|
+
let input;
|
|
2222
|
+
try {
|
|
2223
|
+
input = JSON.parse(json);
|
|
2224
|
+
} catch (error) {
|
|
2225
|
+
throw new ArkConfigValidationError(source, [
|
|
2226
|
+
{
|
|
2227
|
+
path: "$",
|
|
2228
|
+
message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
2229
|
+
}
|
|
2230
|
+
]);
|
|
2231
|
+
}
|
|
2232
|
+
return loadArkConfigContract(input, source);
|
|
2233
|
+
}
|
|
2234
|
+
function withArkConfigMetadata(config) {
|
|
2235
|
+
const result = {
|
|
2236
|
+
$schema: typeof config.$schema === "string" && config.$schema.length > 0 ? config.$schema : ARK_CONFIG_SCHEMA_URL,
|
|
2237
|
+
schemaVersion: ARK_CONFIG_SCHEMA_VERSION
|
|
2238
|
+
};
|
|
2239
|
+
for (const [key, value] of Object.entries(config)) {
|
|
2240
|
+
if (key !== "$schema" && key !== "schemaVersion") result[key] = value;
|
|
2241
|
+
}
|
|
2242
|
+
return result;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
1841
2245
|
// src/kernel/layers/ArchitectureProfile.ts
|
|
1842
2246
|
function normalizePrefix(prefix) {
|
|
1843
2247
|
return prefix.endsWith(".") ? prefix : `${prefix}.`;
|
|
@@ -1847,21 +2251,6 @@ function byLongestPrefix(a, b) {
|
|
|
1847
2251
|
const maxB = b.prefixes.length ? Math.max(...b.prefixes.map((p) => p.length)) : 0;
|
|
1848
2252
|
return maxB - maxA;
|
|
1849
2253
|
}
|
|
1850
|
-
function flowKey2(from, to) {
|
|
1851
|
-
return `${from}->${to}`;
|
|
1852
|
-
}
|
|
1853
|
-
function createStrictDenyRules(layers, allowedFlows) {
|
|
1854
|
-
const allowed = new Set(allowedFlows.map((flow) => flowKey2(flow.from, flow.to)));
|
|
1855
|
-
const rules = [];
|
|
1856
|
-
for (const from of layers) {
|
|
1857
|
-
for (const to of layers) {
|
|
1858
|
-
if (from.name === to.name) continue;
|
|
1859
|
-
if (allowed.has(flowKey2(from.name, to.name))) continue;
|
|
1860
|
-
rules.push({ from: from.name, to: to.name, allowed: false });
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
return rules;
|
|
1864
|
-
}
|
|
1865
2254
|
function createArchitectureProfile(options) {
|
|
1866
2255
|
const layers = options.layers.map((layer) => ({
|
|
1867
2256
|
...layer,
|
|
@@ -1960,20 +2349,10 @@ var elevenLayerProfileLayers = [
|
|
|
1960
2349
|
order: 11
|
|
1961
2350
|
}
|
|
1962
2351
|
];
|
|
1963
|
-
var elevenLayerAllowedFlows = [
|
|
1964
|
-
{ from: "PresentationAdapters", to: "ApplicationOrchestration" },
|
|
1965
|
-
{ from: "ApplicationOrchestration", to: "DomainModel" },
|
|
1966
|
-
{ from: "WorkflowSagaEngine", to: "ApplicationOrchestration" },
|
|
1967
|
-
{ from: "WorkflowSagaEngine", to: "DomainModel" },
|
|
1968
|
-
{ from: "BackgroundJobsScheduling", to: "ApplicationOrchestration" }
|
|
1969
|
-
];
|
|
1970
2352
|
var elevenLayerProfile = createArchitectureProfile({
|
|
1971
2353
|
name: "Ark 11-layer Hexagonal Event-Driven Profile",
|
|
1972
2354
|
layers: elevenLayerProfileLayers,
|
|
1973
|
-
rules:
|
|
1974
|
-
elevenLayerProfileLayers,
|
|
1975
|
-
elevenLayerAllowedFlows
|
|
1976
|
-
)
|
|
2355
|
+
rules: DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule }))
|
|
1977
2356
|
});
|
|
1978
2357
|
var defaultElevenLayerDirectories = {
|
|
1979
2358
|
DomainModel: ["domain"],
|
|
@@ -1997,7 +2376,7 @@ function createElevenLayerArkConfig(options = {}) {
|
|
|
1997
2376
|
const rootDir = options.rootDir ?? "src";
|
|
1998
2377
|
const optional = options.optionalLayers ?? true;
|
|
1999
2378
|
const prefix = rootDir === "." ? "" : `${rootDir}/`;
|
|
2000
|
-
return {
|
|
2379
|
+
return withArkConfigMetadata({
|
|
2001
2380
|
include: options.include ?? [rootDir],
|
|
2002
2381
|
layers: elevenLayerProfile.layers.map((layer) => ({
|
|
2003
2382
|
name: layer.name,
|
|
@@ -2008,7 +2387,7 @@ function createElevenLayerArkConfig(options = {}) {
|
|
|
2008
2387
|
optional
|
|
2009
2388
|
})),
|
|
2010
2389
|
rules: [...elevenLayerProfile.rules]
|
|
2011
|
-
};
|
|
2390
|
+
});
|
|
2012
2391
|
}
|
|
2013
2392
|
|
|
2014
2393
|
// src/kernel/metadata/MetadataRegistry.ts
|
|
@@ -2212,7 +2591,11 @@ function extractModuleSpecifiersAst(ts, source) {
|
|
|
2212
2591
|
"export",
|
|
2213
2592
|
Boolean(node.isTypeOnly || specifiersOnly)
|
|
2214
2593
|
);
|
|
2215
|
-
} else if (ts.
|
|
2594
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
|
|
2595
|
+
const argument = node.moduleReference.expression;
|
|
2596
|
+
const value = tsStringLiteralText(ts, argument);
|
|
2597
|
+
if (value !== void 0) push(argument, value, "require");
|
|
2598
|
+
} else if (ts.isCallExpression(node)) {
|
|
2216
2599
|
const argument = node.arguments[0];
|
|
2217
2600
|
const value = tsStringLiteralText(ts, argument);
|
|
2218
2601
|
if (value !== void 0 && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
@@ -2226,17 +2609,25 @@ function extractModuleSpecifiersAst(ts, source) {
|
|
|
2226
2609
|
visit(sourceFile);
|
|
2227
2610
|
return matches.sort((a, b) => a.index - b.index);
|
|
2228
2611
|
}
|
|
2229
|
-
function
|
|
2612
|
+
function nonLiteralDynamicDependencies(ts, source) {
|
|
2230
2613
|
const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
|
|
2231
|
-
const
|
|
2614
|
+
const uses = [];
|
|
2232
2615
|
const visit = (node) => {
|
|
2233
|
-
if (ts.isCallExpression(node)
|
|
2234
|
-
|
|
2616
|
+
if (ts.isCallExpression(node)) {
|
|
2617
|
+
const dynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
2618
|
+
const directRequire = ts.isIdentifier(node.expression) && node.expression.text === "require";
|
|
2619
|
+
const argument = node.arguments[0];
|
|
2620
|
+
if ((dynamicImport || directRequire) && (!argument || !ts.isStringLiteralLike(argument))) {
|
|
2621
|
+
uses.push({
|
|
2622
|
+
line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
2623
|
+
kind: directRequire ? "require" : "import"
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2235
2626
|
}
|
|
2236
2627
|
ts.forEachChild(node, visit);
|
|
2237
2628
|
};
|
|
2238
2629
|
visit(sourceFile);
|
|
2239
|
-
return
|
|
2630
|
+
return uses;
|
|
2240
2631
|
}
|
|
2241
2632
|
function extractQuotedStringsAst(ts, source) {
|
|
2242
2633
|
const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
|
|
@@ -2347,10 +2738,45 @@ function tsPublishSourceLiteral(ts, node) {
|
|
|
2347
2738
|
const rawMetadata = tsObjectPropertyValue(ts, firstArg, "metadata");
|
|
2348
2739
|
return tsStringLiteralText(ts, tsObjectPropertyValue(ts, rawMetadata, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, secondArg, "source")) ?? tsStringLiteralText(ts, tsObjectPropertyValue(ts, thirdArg, "source"));
|
|
2349
2740
|
}
|
|
2741
|
+
function singleFileTypeChecker(ts, sourceFile) {
|
|
2742
|
+
const options = {
|
|
2743
|
+
noLib: true,
|
|
2744
|
+
noResolve: true,
|
|
2745
|
+
target: ts.ScriptTarget.Latest
|
|
2746
|
+
};
|
|
2747
|
+
const host = ts.createCompilerHost(options, true);
|
|
2748
|
+
host.getSourceFile = (fileName) => fileName === sourceFile.fileName ? sourceFile : void 0;
|
|
2749
|
+
host.fileExists = (fileName) => fileName === sourceFile.fileName;
|
|
2750
|
+
host.readFile = (fileName) => fileName === sourceFile.fileName ? sourceFile.text : void 0;
|
|
2751
|
+
return ts.createProgram([sourceFile.fileName], options, host).getTypeChecker();
|
|
2752
|
+
}
|
|
2753
|
+
function propertyAccessPath(ts, node) {
|
|
2754
|
+
const segments = [];
|
|
2755
|
+
let current = node;
|
|
2756
|
+
while (ts.isPropertyAccessExpression(current)) {
|
|
2757
|
+
segments.unshift(current.name.text);
|
|
2758
|
+
current = current.expression;
|
|
2759
|
+
}
|
|
2760
|
+
if (!ts.isIdentifier(current)) return void 0;
|
|
2761
|
+
segments.unshift(current.text);
|
|
2762
|
+
return { root: current, segments };
|
|
2763
|
+
}
|
|
2764
|
+
function isRuntimeIdentifierReference(ts, node) {
|
|
2765
|
+
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) return false;
|
|
2766
|
+
return ts.isExpressionNode(node) && !ts.isInTypeQuery(node) || ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node;
|
|
2767
|
+
}
|
|
2768
|
+
function hasLocalDeclaration(ts, checker, sourceFile, node) {
|
|
2769
|
+
const shorthand = ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node;
|
|
2770
|
+
const symbol = shorthand ? checker.getShorthandAssignmentValueSymbol(node.parent) : checker.getSymbolAtLocation(node);
|
|
2771
|
+
return Boolean(
|
|
2772
|
+
symbol?.declarations?.some((declaration) => declaration.getSourceFile() === sourceFile)
|
|
2773
|
+
);
|
|
2774
|
+
}
|
|
2350
2775
|
function analyzeForbiddenGlobals(ts, source, filePath, layer, forbidden) {
|
|
2351
2776
|
const entries = new Set(forbidden);
|
|
2352
2777
|
if (entries.size === 0) return [];
|
|
2353
2778
|
const sourceFile = ts.createSourceFile("generated.ts", source, ts.ScriptTarget.Latest, true);
|
|
2779
|
+
const checker = singleFileTypeChecker(ts, sourceFile);
|
|
2354
2780
|
const violations = [];
|
|
2355
2781
|
const flag = (name, node) => violations.push(
|
|
2356
2782
|
violation("FORBIDDEN_GLOBAL", `${layer} must not use the ambient global "${name}".`, {
|
|
@@ -2362,12 +2788,24 @@ function analyzeForbiddenGlobals(ts, source, filePath, layer, forbidden) {
|
|
|
2362
2788
|
})
|
|
2363
2789
|
);
|
|
2364
2790
|
const visit = (node) => {
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2791
|
+
const nestedPropertyAccess = ts.isPropertyAccessExpression(node) && ts.isPropertyAccessExpression(node.parent) && node.parent.expression === node;
|
|
2792
|
+
if (ts.isPropertyAccessExpression(node) && !nestedPropertyAccess) {
|
|
2793
|
+
const path = propertyAccessPath(ts, node);
|
|
2794
|
+
if (path && !hasLocalDeclaration(ts, checker, sourceFile, path.root)) {
|
|
2795
|
+
const explicitGlobalThis = path.segments[0] === "globalThis";
|
|
2796
|
+
const normalized = explicitGlobalThis ? path.segments.slice(1) : path.segments;
|
|
2797
|
+
let match;
|
|
2798
|
+
for (let length = normalized.length; length >= (explicitGlobalThis ? 1 : 2); length -= 1) {
|
|
2799
|
+
const candidate = normalized.slice(0, length).join(".");
|
|
2800
|
+
if (entries.has(candidate)) {
|
|
2801
|
+
match = candidate;
|
|
2802
|
+
break;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
if (match) flag(match, node);
|
|
2806
|
+
}
|
|
2807
|
+
} else if (ts.isIdentifier(node) && entries.has(node.text) && isRuntimeIdentifierReference(ts, node) && !hasLocalDeclaration(ts, checker, sourceFile, node)) {
|
|
2808
|
+
flag(node.text, node);
|
|
2371
2809
|
}
|
|
2372
2810
|
ts.forEachChild(node, visit);
|
|
2373
2811
|
};
|
|
@@ -2444,15 +2882,16 @@ function createAICodeGate(options = {}) {
|
|
|
2444
2882
|
const gateContext = context;
|
|
2445
2883
|
const filePath = gateContext?.filePath;
|
|
2446
2884
|
const contextLayer = gateContext?.layer;
|
|
2447
|
-
const
|
|
2885
|
+
const moduleSpecifiers2 = options.typescript ? extractModuleSpecifiersAst(options.typescript, source) : extractModuleSpecifiers(source);
|
|
2448
2886
|
const quotedStrings = options.typescript ? extractQuotedStringsAst(options.typescript, source) : extractQuotedStrings(source);
|
|
2449
2887
|
if (options.typescript && !options.allowNonLiteralDynamicImport?.(filePath)) {
|
|
2450
|
-
for (const
|
|
2888
|
+
for (const dependency of nonLiteralDynamicDependencies(options.typescript, source)) {
|
|
2889
|
+
const isRequire = dependency.kind === "require";
|
|
2451
2890
|
violations.push(
|
|
2452
2891
|
violation(
|
|
2453
|
-
"DYNAMIC_IMPORT_NOT_ALLOWLISTED",
|
|
2454
|
-
|
|
2455
|
-
{ line, filePath }
|
|
2892
|
+
isRequire ? "DYNAMIC_REQUIRE_NOT_ALLOWLISTED" : "DYNAMIC_IMPORT_NOT_ALLOWLISTED",
|
|
2893
|
+
`Non-literal ${isRequire ? "require call" : "dynamic import"} cannot be resolved statically; add the reviewed file to dynamicImportAllowlist.`,
|
|
2894
|
+
{ line: dependency.line, filePath }
|
|
2456
2895
|
)
|
|
2457
2896
|
);
|
|
2458
2897
|
}
|
|
@@ -2482,7 +2921,7 @@ function createAICodeGate(options = {}) {
|
|
|
2482
2921
|
);
|
|
2483
2922
|
}
|
|
2484
2923
|
}
|
|
2485
|
-
for (const specifier of
|
|
2924
|
+
for (const specifier of moduleSpecifiers2) {
|
|
2486
2925
|
const targetHit = options.resolveImportTarget?.(specifier.value, filePath) ?? (options.resolveImportLayer ? { layer: options.resolveImportLayer(specifier.value, filePath) } : void 0);
|
|
2487
2926
|
const sourceHit = typeof filePath === "string" ? options.resolveImportTarget?.(filePath) ?? (options.resolveImportLayer ? { layer: contextLayer, relPath: void 0 } : void 0) : void 0;
|
|
2488
2927
|
const targetLayer = targetHit?.layer;
|
|
@@ -2681,6 +3120,214 @@ function createAICodeGate(options = {}) {
|
|
|
2681
3120
|
};
|
|
2682
3121
|
}
|
|
2683
3122
|
|
|
3123
|
+
// src/domain/analysis.ts
|
|
3124
|
+
var ANALYSIS_IR_SCHEMA_VERSION = "1.0";
|
|
3125
|
+
function deterministicHash(value) {
|
|
3126
|
+
let hash = 2166136261;
|
|
3127
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
3128
|
+
hash ^= value.charCodeAt(index);
|
|
3129
|
+
hash = Math.imul(hash, 16777619);
|
|
3130
|
+
}
|
|
3131
|
+
return `fnv1a-${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
3132
|
+
}
|
|
3133
|
+
function stableSerialize(value) {
|
|
3134
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3135
|
+
if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
|
|
3136
|
+
const object = value;
|
|
3137
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableSerialize(object[key])}`).join(",")}}`;
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
// src/kernel/analysis.ts
|
|
3141
|
+
function loadContract(input, source) {
|
|
3142
|
+
const loaded = typeof input === "string" ? parseArkConfigJson(input, source) : loadArkConfigContract(input, source);
|
|
3143
|
+
return { ...loaded, policyHash: deterministicHash(stableSerialize(loaded.config)) };
|
|
3144
|
+
}
|
|
3145
|
+
function normalizePath(value) {
|
|
3146
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3147
|
+
}
|
|
3148
|
+
function isIdentifierCharacter(value) {
|
|
3149
|
+
return value !== void 0 && /[A-Za-z0-9_$]/.test(value);
|
|
3150
|
+
}
|
|
3151
|
+
function skipWhitespace(source, index) {
|
|
3152
|
+
while (index < source.length && /\s/.test(source[index])) index += 1;
|
|
3153
|
+
return index;
|
|
3154
|
+
}
|
|
3155
|
+
function readString(source, index) {
|
|
3156
|
+
const quote = source[index];
|
|
3157
|
+
if (quote !== "'" && quote !== '"') return void 0;
|
|
3158
|
+
const start = index;
|
|
3159
|
+
let value = "";
|
|
3160
|
+
for (index += 1; index < source.length; index += 1) {
|
|
3161
|
+
const current = source[index];
|
|
3162
|
+
if (current === quote) {
|
|
3163
|
+
return { value, offset: start, excerpt: source.slice(start, index + 1) };
|
|
3164
|
+
}
|
|
3165
|
+
if (current === "\\" && index + 1 < source.length) {
|
|
3166
|
+
value += source[index + 1];
|
|
3167
|
+
index += 1;
|
|
3168
|
+
} else {
|
|
3169
|
+
value += current;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return void 0;
|
|
3173
|
+
}
|
|
3174
|
+
function isWordAt(source, word, index) {
|
|
3175
|
+
return source.startsWith(word, index) && !isIdentifierCharacter(source[index - 1]) && !isIdentifierCharacter(source[index + word.length]);
|
|
3176
|
+
}
|
|
3177
|
+
function specifierAfterImport(source, index) {
|
|
3178
|
+
index = skipWhitespace(source, index + "import".length);
|
|
3179
|
+
if (source[index] === "(") return readString(source, skipWhitespace(source, index + 1));
|
|
3180
|
+
return specifierInStaticStatement(source, index, true);
|
|
3181
|
+
}
|
|
3182
|
+
function specifierAfterExport(source, index) {
|
|
3183
|
+
return specifierInStaticStatement(source, index + "export".length, false);
|
|
3184
|
+
}
|
|
3185
|
+
function specifierInStaticStatement(source, index, allowDirectSpecifier) {
|
|
3186
|
+
for (; index < source.length; index += 1) {
|
|
3187
|
+
if (source[index] === ";") return void 0;
|
|
3188
|
+
if (isWordAt(source, "from", index)) {
|
|
3189
|
+
return readString(source, skipWhitespace(source, index + "from".length));
|
|
3190
|
+
}
|
|
3191
|
+
if (allowDirectSpecifier && (source[index] === "'" || source[index] === '"')) {
|
|
3192
|
+
return readString(source, index);
|
|
3193
|
+
}
|
|
3194
|
+
if (index > 0 && (isWordAt(source, "import", index) || isWordAt(source, "export", index))) {
|
|
3195
|
+
return void 0;
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
return void 0;
|
|
3199
|
+
}
|
|
3200
|
+
function moduleSpecifiers(source) {
|
|
3201
|
+
const result = [];
|
|
3202
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
3203
|
+
const current = source[index];
|
|
3204
|
+
if (current === "/" && source[index + 1] === "/") {
|
|
3205
|
+
index = source.indexOf("\n", index + 2);
|
|
3206
|
+
if (index < 0) break;
|
|
3207
|
+
continue;
|
|
3208
|
+
}
|
|
3209
|
+
if (current === "/" && source[index + 1] === "*") {
|
|
3210
|
+
const end = source.indexOf("*/", index + 2);
|
|
3211
|
+
if (end < 0) break;
|
|
3212
|
+
index = end + 1;
|
|
3213
|
+
continue;
|
|
3214
|
+
}
|
|
3215
|
+
if (current === "'" || current === '"' || current === "`") {
|
|
3216
|
+
const string = readString(source, index);
|
|
3217
|
+
if (string) index = string.offset + string.excerpt.length - 1;
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3220
|
+
const specifier = isWordAt(source, "import", index) ? specifierAfterImport(source, index) : isWordAt(source, "export", index) ? specifierAfterExport(source, index) : void 0;
|
|
3221
|
+
if (specifier) result.push(specifier);
|
|
3222
|
+
}
|
|
3223
|
+
return result;
|
|
3224
|
+
}
|
|
3225
|
+
function importEdges(file, files) {
|
|
3226
|
+
const edges = [];
|
|
3227
|
+
for (const moduleSpecifier of moduleSpecifiers(file.content)) {
|
|
3228
|
+
const specifier = moduleSpecifier.value;
|
|
3229
|
+
if (!specifier.startsWith(".")) continue;
|
|
3230
|
+
const line = file.content.slice(0, moduleSpecifier.offset).split("\n").length;
|
|
3231
|
+
const evidence = {
|
|
3232
|
+
kind: "import",
|
|
3233
|
+
file: file.path,
|
|
3234
|
+
line,
|
|
3235
|
+
excerpt: moduleSpecifier.excerpt
|
|
3236
|
+
};
|
|
3237
|
+
const target = resolveSpecifier(file.path, specifier, files);
|
|
3238
|
+
edges.push({
|
|
3239
|
+
from: file.path,
|
|
3240
|
+
specifier,
|
|
3241
|
+
to: target?.path ?? null,
|
|
3242
|
+
resolution: target ? "resolved" : "unresolved",
|
|
3243
|
+
fromLayer: file.layer,
|
|
3244
|
+
toLayer: target?.layer ?? null,
|
|
3245
|
+
evidence
|
|
3246
|
+
});
|
|
3247
|
+
}
|
|
3248
|
+
return edges;
|
|
3249
|
+
}
|
|
3250
|
+
function resolveSpecifier(from, specifier, files) {
|
|
3251
|
+
const segments = from.split("/");
|
|
3252
|
+
segments.pop();
|
|
3253
|
+
for (const segment of specifier.split("/")) {
|
|
3254
|
+
if (segment === "." || segment === "") continue;
|
|
3255
|
+
if (segment === "..") segments.pop();
|
|
3256
|
+
else segments.push(segment);
|
|
3257
|
+
}
|
|
3258
|
+
const base = segments.join("/");
|
|
3259
|
+
for (const candidate of [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, `${base}.cts`, `${base}/index.ts`, `${base}/index.tsx`]) {
|
|
3260
|
+
const found = files.get(candidate);
|
|
3261
|
+
if (found) return found;
|
|
3262
|
+
}
|
|
3263
|
+
return void 0;
|
|
3264
|
+
}
|
|
3265
|
+
function violationsFor(edges, config) {
|
|
3266
|
+
const violations = [];
|
|
3267
|
+
for (const edge of edges) {
|
|
3268
|
+
if (!edge.to || !edge.fromLayer || !edge.toLayer) continue;
|
|
3269
|
+
const rule = findDeniedEdgeRule(config.rules, edge.fromLayer, edge.toLayer, {
|
|
3270
|
+
fromPath: edge.from,
|
|
3271
|
+
toPath: edge.to,
|
|
3272
|
+
layers: config.layers
|
|
3273
|
+
});
|
|
3274
|
+
if (!rule) continue;
|
|
3275
|
+
violations.push({
|
|
3276
|
+
ruleId: `layer-dependency:${rule.from}->${rule.to}`,
|
|
3277
|
+
message: rule.message ?? `${rule.from} must not depend on ${rule.to}.`,
|
|
3278
|
+
edge,
|
|
3279
|
+
evidence: edge.evidence
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
return violations;
|
|
3283
|
+
}
|
|
3284
|
+
function analyzeProject(input) {
|
|
3285
|
+
const files = input.files.map((inputFile) => {
|
|
3286
|
+
const path = normalizePath(inputFile.path);
|
|
3287
|
+
return {
|
|
3288
|
+
path,
|
|
3289
|
+
content: inputFile.content,
|
|
3290
|
+
contentHash: deterministicHash(inputFile.content),
|
|
3291
|
+
layer: layerForRelativePath(path, input.contract.config.layers) ?? null
|
|
3292
|
+
};
|
|
3293
|
+
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
3294
|
+
const fileByPath = new Map(files.map((file) => [file.path, file]));
|
|
3295
|
+
const edges = files.flatMap((file) => importEdges(file, fileByPath));
|
|
3296
|
+
const capabilityUses = [];
|
|
3297
|
+
const violations = violationsFor(edges, input.contract.config);
|
|
3298
|
+
return {
|
|
3299
|
+
ir: {
|
|
3300
|
+
schemaVersion: ANALYSIS_IR_SCHEMA_VERSION,
|
|
3301
|
+
policyHash: input.contract.policyHash,
|
|
3302
|
+
compilerOptionsHash: deterministicHash(stableSerialize(input.compilerOptions ?? {})),
|
|
3303
|
+
files,
|
|
3304
|
+
layers: input.contract.config.layers.map((layer) => layer.name),
|
|
3305
|
+
edges,
|
|
3306
|
+
capabilityUses,
|
|
3307
|
+
violations
|
|
3308
|
+
}
|
|
3309
|
+
};
|
|
3310
|
+
}
|
|
3311
|
+
function analyzeChange(input) {
|
|
3312
|
+
const files = new Map(input.files.map((file) => [normalizePath(file.path), file]));
|
|
3313
|
+
for (const change of input.changes) {
|
|
3314
|
+
const path = normalizePath(change.path);
|
|
3315
|
+
if ("delete" in change && change.delete) files.delete(path);
|
|
3316
|
+
else if ("content" in change) files.set(path, { path, content: change.content });
|
|
3317
|
+
}
|
|
3318
|
+
return analyzeProject({
|
|
3319
|
+
contract: input.contract,
|
|
3320
|
+
files: [...files.values()],
|
|
3321
|
+
compilerOptions: input.compilerOptions
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
function explainViolation(violation2) {
|
|
3325
|
+
const location = `${violation2.evidence.file}:${violation2.evidence.line}`;
|
|
3326
|
+
if (!violation2.edge) return `${violation2.ruleId} at ${location}: ${violation2.message}`;
|
|
3327
|
+
const target = violation2.edge.to ?? violation2.edge.specifier;
|
|
3328
|
+
return `${violation2.ruleId} at ${location}: ${violation2.edge.from} imports ${target}. ${violation2.message}`;
|
|
3329
|
+
}
|
|
3330
|
+
|
|
2684
3331
|
// src/kernel/projections/ProjectionRegistry.ts
|
|
2685
3332
|
var InMemoryReadModelStore = class {
|
|
2686
3333
|
states = /* @__PURE__ */ new Map();
|
|
@@ -2989,22 +3636,13 @@ var WorkflowEngineImpl = class {
|
|
|
2989
3636
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2990
3637
|
snapshot.attempts[step.name] = attempt;
|
|
2991
3638
|
await this.store.save(snapshot);
|
|
3639
|
+
let result;
|
|
2992
3640
|
try {
|
|
2993
|
-
|
|
3641
|
+
result = await withTimeout(
|
|
2994
3642
|
(signal) => Promise.resolve(step.execute(snapshot.context, this.bus, signal)),
|
|
2995
3643
|
step.timeoutMs,
|
|
2996
3644
|
step.name
|
|
2997
3645
|
);
|
|
2998
|
-
if (result) Object.assign(snapshot.context, result);
|
|
2999
|
-
snapshot.completedSteps.push(step.name);
|
|
3000
|
-
snapshot.currentStep = void 0;
|
|
3001
|
-
snapshot.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3002
|
-
await this.store.save(snapshot);
|
|
3003
|
-
await this.audit("workflow.step.completed", snapshot, {
|
|
3004
|
-
step: step.name,
|
|
3005
|
-
attempt
|
|
3006
|
-
});
|
|
3007
|
-
return;
|
|
3008
3646
|
} catch (err) {
|
|
3009
3647
|
if (attempt < maxAttempts) {
|
|
3010
3648
|
if (retry.delayMs) await sleep(retry.delayMs);
|
|
@@ -3020,6 +3658,16 @@ var WorkflowEngineImpl = class {
|
|
|
3020
3658
|
});
|
|
3021
3659
|
throw err;
|
|
3022
3660
|
}
|
|
3661
|
+
if (result) Object.assign(snapshot.context, result);
|
|
3662
|
+
snapshot.completedSteps.push(step.name);
|
|
3663
|
+
snapshot.currentStep = void 0;
|
|
3664
|
+
snapshot.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3665
|
+
await this.store.save(snapshot);
|
|
3666
|
+
await this.audit("workflow.step.completed", snapshot, {
|
|
3667
|
+
step: step.name,
|
|
3668
|
+
attempt
|
|
3669
|
+
});
|
|
3670
|
+
return;
|
|
3023
3671
|
}
|
|
3024
3672
|
throw new Error(`Workflow step "${step.name}" did not complete.`);
|
|
3025
3673
|
}
|
|
@@ -3210,6 +3858,7 @@ function createLenientArkKernel(options = {}) {
|
|
|
3210
3858
|
});
|
|
3211
3859
|
}
|
|
3212
3860
|
export {
|
|
3861
|
+
ANALYSIS_IR_SCHEMA_VERSION,
|
|
3213
3862
|
DEFAULT_MAX_HISTORY_SIZE,
|
|
3214
3863
|
EventContractRegistryImpl,
|
|
3215
3864
|
EventContractViolationError,
|
|
@@ -3227,6 +3876,8 @@ export {
|
|
|
3227
3876
|
SourceMetadataOverrideError,
|
|
3228
3877
|
UnknownEventSourceError,
|
|
3229
3878
|
UnregisteredIntentError,
|
|
3879
|
+
analyzeChange,
|
|
3880
|
+
analyzeProject,
|
|
3230
3881
|
architecturalPolicies,
|
|
3231
3882
|
buildPublishPolicyContext,
|
|
3232
3883
|
checkAdapterGovernance,
|
|
@@ -3261,8 +3912,12 @@ export {
|
|
|
3261
3912
|
definePolicy,
|
|
3262
3913
|
definePort,
|
|
3263
3914
|
definePublishPolicy,
|
|
3915
|
+
deterministicHash,
|
|
3264
3916
|
elevenLayerProfile,
|
|
3917
|
+
explainViolation,
|
|
3265
3918
|
isLayerPolicy,
|
|
3919
|
+
loadContract,
|
|
3920
|
+
stableSerialize,
|
|
3266
3921
|
syncRegistryToGraph,
|
|
3267
3922
|
validateIntentName,
|
|
3268
3923
|
version
|