arkgate 2.12.0 → 3.0.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 +122 -0
- package/README.md +90 -51
- package/bin/ark-check.mjs +156 -39
- package/bin/ark-mcp.mjs +119 -6
- package/bin/ark-shared.mjs +216 -129
- package/bin/ark.mjs +134 -34
- package/bin/lib/adapter-contract.mjs +93 -0
- package/bin/lib/agent-gates.mjs +13 -0
- package/bin/lib/analysis-engine.mjs +1171 -0
- package/bin/lib/architecture-scan.mjs +84 -127
- package/bin/lib/ci-and-commands.mjs +40 -3
- package/bin/lib/codex-home.mjs +7 -0
- package/bin/lib/config-contract.mjs +331 -0
- package/bin/lib/config-warnings.mjs +7 -205
- package/bin/lib/doctor-plan.mjs +43 -16
- package/bin/lib/enforcement-profiles.mjs +97 -0
- package/bin/lib/field-install.mjs +67 -10
- package/bin/lib/gate-files.mjs +42 -3
- package/bin/lib/graph-cycles.mjs +4 -54
- package/bin/lib/hook-templates.mjs +33 -1
- package/bin/lib/host-support-matrix.mjs +83 -0
- package/bin/lib/install-migrate.mjs +99 -30
- package/bin/lib/mcp-adoption.mjs +35 -3
- package/bin/lib/open-html.mjs +75 -0
- package/bin/lib/presets.mjs +45 -4
- package/bin/lib/safety-diagnostics.mjs +36 -15
- package/bin/lib/scan-files.mjs +12 -1
- package/bin/lib/skill-install.mjs +72 -1
- package/bin/lib/source-policy.mjs +36 -0
- package/bin/lib/start-preview.mjs +271 -0
- package/bin/lib/ts-resolve.mjs +13 -3
- package/bin/lib/weakest-link.mjs +417 -0
- package/bin/lib/write-path-capabilities.mjs +186 -0
- package/bin/lib/write-path-detect.mjs +62 -99
- package/compat/nestjs.cjs +2 -0
- package/compat/nestjs.d.ts +2 -0
- package/compat/nestjs.js +1 -0
- package/compat/runtime.cjs +2 -0
- package/compat/runtime.d.ts +2 -0
- package/compat/runtime.js +1 -0
- package/dist/configContract-BxSIwVRo.d.cts +259 -0
- package/dist/configContract-BxSIwVRo.d.ts +259 -0
- package/dist/eslint/index.cjs +500 -61
- package/dist/eslint/index.d.cts +36 -20
- package/dist/eslint/index.d.ts +36 -20
- package/dist/eslint/index.js +500 -61
- package/dist/index.cjs +1349 -2741
- package/dist/index.d.cts +449 -483
- package/dist/index.d.ts +449 -483
- package/dist/index.js +1325 -2687
- package/docs/agent-guide.md +58 -34
- package/docs/ai-gates.md +79 -21
- 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 +5 -3
- package/docs/package-surface.md +19 -19
- package/docs/production-hardening.md +31 -5
- package/docs/threat-model.md +65 -0
- package/docs/typescript-support.md +30 -3
- package/package.json +46 -11
- package/schemas/ark.analysis-result.schema.json +91 -0
- 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-architect.md +3 -2
- 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/eslint/index.cjs.map +0 -1
- package/dist/eslint/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/nestjs/index.cjs +0 -2498
- package/dist/nestjs/index.cjs.map +0 -1
- package/dist/nestjs/index.d.cts +0 -22
- package/dist/nestjs/index.d.ts +0 -22
- package/dist/nestjs/index.js +0 -2474
- package/dist/nestjs/index.js.map +0 -1
- package/dist/runtime/index.cjs +0 -3352
- package/dist/runtime/index.cjs.map +0 -1
- package/dist/runtime/index.d.cts +0 -2
- package/dist/runtime/index.d.ts +0 -2
- package/dist/runtime/index.js +0 -3270
- package/dist/runtime/index.js.map +0 -1
- package/dist/types-BZ17b9i5.d.cts +0 -1068
- package/dist/types-BZ17b9i5.d.ts +0 -1068
package/dist/eslint/index.js
CHANGED
|
@@ -170,6 +170,368 @@ function isEdgeDenied(rules2, from, to, options) {
|
|
|
170
170
|
return findDeniedEdgeRule(rules2, from, to, options) !== void 0;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
// src/domain/configContract.ts
|
|
174
|
+
var ARK_CONFIG_SCHEMA_VERSION = "1.0";
|
|
175
|
+
var ARK_CONFIG_SCHEMA_URL = "https://unpkg.com/arkgate@2/schemas/ark.config.schema.json";
|
|
176
|
+
var DEFAULT_LAYER_NAMES = [
|
|
177
|
+
"DomainModel",
|
|
178
|
+
"ApplicationOrchestration",
|
|
179
|
+
"PersistenceAdapters",
|
|
180
|
+
"IntegrationAdapters",
|
|
181
|
+
"WorkflowSagaEngine",
|
|
182
|
+
"BackgroundJobsScheduling",
|
|
183
|
+
"PresentationAdapters",
|
|
184
|
+
"ReportingReadModels",
|
|
185
|
+
"ExtensibilityMetadata",
|
|
186
|
+
"SecurityAuditObservability",
|
|
187
|
+
"Kernel"
|
|
188
|
+
];
|
|
189
|
+
var DEFAULT_ALLOWED_FLOWS = /* @__PURE__ */ new Set([
|
|
190
|
+
"PresentationAdapters->ApplicationOrchestration",
|
|
191
|
+
"ApplicationOrchestration->DomainModel",
|
|
192
|
+
"WorkflowSagaEngine->ApplicationOrchestration",
|
|
193
|
+
"WorkflowSagaEngine->DomainModel",
|
|
194
|
+
"BackgroundJobsScheduling->ApplicationOrchestration"
|
|
195
|
+
]);
|
|
196
|
+
function createDefaultRules() {
|
|
197
|
+
const rules2 = [];
|
|
198
|
+
for (const from of DEFAULT_LAYER_NAMES) {
|
|
199
|
+
for (const to of DEFAULT_LAYER_NAMES) {
|
|
200
|
+
if (from === to || DEFAULT_ALLOWED_FLOWS.has(`${from}->${to}`)) continue;
|
|
201
|
+
rules2.push({ from, to, allowed: false });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return rules2;
|
|
205
|
+
}
|
|
206
|
+
var DEFAULT_ARK_CONFIG_RULES = createDefaultRules();
|
|
207
|
+
var stringArraySchema = {
|
|
208
|
+
type: "array",
|
|
209
|
+
items: { type: "string", minLength: 1 },
|
|
210
|
+
uniqueItems: true
|
|
211
|
+
};
|
|
212
|
+
var ARK_CONFIG_SCHEMA = {
|
|
213
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
214
|
+
$id: ARK_CONFIG_SCHEMA_URL,
|
|
215
|
+
title: "ArkGate architecture contract",
|
|
216
|
+
description: "Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",
|
|
217
|
+
type: "object",
|
|
218
|
+
additionalProperties: false,
|
|
219
|
+
required: ["$schema", "schemaVersion", "include", "layers", "rules"],
|
|
220
|
+
properties: {
|
|
221
|
+
$schema: {
|
|
222
|
+
type: "string",
|
|
223
|
+
minLength: 1,
|
|
224
|
+
default: ARK_CONFIG_SCHEMA_URL,
|
|
225
|
+
description: "Editor-facing URL or local path for this JSON Schema."
|
|
226
|
+
},
|
|
227
|
+
schemaVersion: {
|
|
228
|
+
type: "string",
|
|
229
|
+
const: ARK_CONFIG_SCHEMA_VERSION,
|
|
230
|
+
default: ARK_CONFIG_SCHEMA_VERSION
|
|
231
|
+
},
|
|
232
|
+
name: { type: "string", minLength: 1 },
|
|
233
|
+
include: { ...stringArraySchema, minItems: 1, default: ["src"] },
|
|
234
|
+
exclude: { ...stringArraySchema, default: [] },
|
|
235
|
+
excludeGenerated: { type: "boolean", default: true },
|
|
236
|
+
frameworkOverlay: { type: "string", minLength: 1 },
|
|
237
|
+
layers: {
|
|
238
|
+
type: "array",
|
|
239
|
+
default: [],
|
|
240
|
+
items: { $ref: "#/$defs/layer" }
|
|
241
|
+
},
|
|
242
|
+
rules: {
|
|
243
|
+
type: "array",
|
|
244
|
+
default: DEFAULT_ARK_CONFIG_RULES,
|
|
245
|
+
items: { $ref: "#/$defs/rule" }
|
|
246
|
+
},
|
|
247
|
+
cyclePolicy: {
|
|
248
|
+
type: "string",
|
|
249
|
+
enum: ["strict", "soft", "framework-soft", "off"],
|
|
250
|
+
default: "strict"
|
|
251
|
+
},
|
|
252
|
+
dynamicImportAllowlist: { ...stringArraySchema, default: [] },
|
|
253
|
+
safety: {
|
|
254
|
+
$ref: "#/$defs/safety",
|
|
255
|
+
default: {
|
|
256
|
+
maxTsSuppressions: 0,
|
|
257
|
+
maxAnyCasts: 0,
|
|
258
|
+
allowInMemory: false,
|
|
259
|
+
allowDisabledPeerIsolation: false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
$defs: {
|
|
264
|
+
layer: {
|
|
265
|
+
type: "object",
|
|
266
|
+
additionalProperties: false,
|
|
267
|
+
required: ["name", "patterns"],
|
|
268
|
+
properties: {
|
|
269
|
+
name: { type: "string", minLength: 1 },
|
|
270
|
+
patterns: { ...stringArraySchema, minItems: 1 },
|
|
271
|
+
exclude: stringArraySchema,
|
|
272
|
+
intentPrefixes: stringArraySchema,
|
|
273
|
+
description: { type: "string", minLength: 1 },
|
|
274
|
+
forbiddenGlobals: stringArraySchema,
|
|
275
|
+
mayImportInfrastructure: { type: "boolean" },
|
|
276
|
+
optional: { type: "boolean" }
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
rule: {
|
|
280
|
+
type: "object",
|
|
281
|
+
additionalProperties: false,
|
|
282
|
+
required: ["from", "to", "allowed"],
|
|
283
|
+
properties: {
|
|
284
|
+
from: { type: "string", minLength: 1 },
|
|
285
|
+
to: { type: "string", minLength: 1 },
|
|
286
|
+
allowed: { type: "boolean" },
|
|
287
|
+
message: { type: "string", minLength: 1 },
|
|
288
|
+
peerIsolation: { type: "boolean" },
|
|
289
|
+
sliceFolders: { ...stringArraySchema, minItems: 1 }
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
safety: {
|
|
293
|
+
type: "object",
|
|
294
|
+
additionalProperties: false,
|
|
295
|
+
properties: {
|
|
296
|
+
maxTsSuppressions: { type: "integer", minimum: 0, default: 0 },
|
|
297
|
+
maxAnyCasts: { type: "integer", minimum: 0, default: 0 },
|
|
298
|
+
allowInMemory: { type: "boolean", default: false },
|
|
299
|
+
allowDisabledPeerIsolation: { type: "boolean", default: false }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
var ArkConfigValidationError = class extends Error {
|
|
305
|
+
issues;
|
|
306
|
+
source;
|
|
307
|
+
constructor(source, issues) {
|
|
308
|
+
super(
|
|
309
|
+
`Invalid ArkGate config (${source}):
|
|
310
|
+
${issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n")}`
|
|
311
|
+
);
|
|
312
|
+
this.name = "ArkConfigValidationError";
|
|
313
|
+
this.source = source;
|
|
314
|
+
this.issues = issues;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
function isObject(value) {
|
|
318
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
319
|
+
}
|
|
320
|
+
function propertyPath(parent, key) {
|
|
321
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
|
|
322
|
+
}
|
|
323
|
+
function valueType(value) {
|
|
324
|
+
if (value === null) return "null";
|
|
325
|
+
if (Array.isArray(value)) return "array";
|
|
326
|
+
return typeof value;
|
|
327
|
+
}
|
|
328
|
+
function resolveSchemaRef(ref, root) {
|
|
329
|
+
const prefix = "#/$defs/";
|
|
330
|
+
if (!ref.startsWith(prefix)) return void 0;
|
|
331
|
+
return root.$defs[ref.slice(prefix.length)];
|
|
332
|
+
}
|
|
333
|
+
function validateNode(value, schema, path2, root, issues) {
|
|
334
|
+
if (schema.$ref) {
|
|
335
|
+
const referenced = resolveSchemaRef(schema.$ref, root);
|
|
336
|
+
if (!referenced) {
|
|
337
|
+
issues.push({ path: path2, message: `schema reference ${schema.$ref} cannot be resolved` });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
validateNode(value, referenced, path2, root, issues);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (schema.const !== void 0 && !Object.is(value, schema.const)) {
|
|
344
|
+
issues.push({ path: path2, message: `must equal ${JSON.stringify(schema.const)}` });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
|
|
348
|
+
issues.push({ path: path2, message: `must be one of ${schema.enum.map(String).join(", ")}` });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (schema.type === "object") {
|
|
352
|
+
if (!isObject(value)) {
|
|
353
|
+
issues.push({ path: path2, message: `must be an object; received ${valueType(value)}` });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const properties = schema.properties ?? {};
|
|
357
|
+
for (const key of schema.required ?? []) {
|
|
358
|
+
if (value[key] === void 0) {
|
|
359
|
+
issues.push({ path: propertyPath(path2, key), message: "is required" });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (schema.additionalProperties === false) {
|
|
363
|
+
for (const key of Object.keys(value)) {
|
|
364
|
+
if (!(key in properties)) {
|
|
365
|
+
issues.push({ path: propertyPath(path2, key), message: "unknown field" });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const [key, childSchema] of Object.entries(properties)) {
|
|
370
|
+
if (value[key] !== void 0) {
|
|
371
|
+
validateNode(value[key], childSchema, propertyPath(path2, key), root, issues);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (schema.type === "array") {
|
|
377
|
+
if (!Array.isArray(value)) {
|
|
378
|
+
issues.push({ path: path2, message: `must be an array; received ${valueType(value)}` });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
382
|
+
issues.push({ path: path2, message: `must contain at least ${schema.minItems} item(s)` });
|
|
383
|
+
}
|
|
384
|
+
if (schema.uniqueItems) {
|
|
385
|
+
const serialized = value.map((entry) => JSON.stringify(entry));
|
|
386
|
+
if (new Set(serialized).size !== serialized.length) {
|
|
387
|
+
issues.push({ path: path2, message: "must not contain duplicate items" });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (schema.items) {
|
|
391
|
+
value.forEach(
|
|
392
|
+
(entry, index) => validateNode(entry, schema.items, `${path2}[${index}]`, root, issues)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (schema.type === "string") {
|
|
398
|
+
if (typeof value !== "string") {
|
|
399
|
+
issues.push({ path: path2, message: `must be a string; received ${valueType(value)}` });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
403
|
+
issues.push({ path: path2, message: `must contain at least ${schema.minLength} character(s)` });
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (schema.type === "boolean") {
|
|
408
|
+
if (typeof value !== "boolean") {
|
|
409
|
+
issues.push({ path: path2, message: `must be a boolean; received ${valueType(value)}` });
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (schema.type === "integer") {
|
|
414
|
+
if (!Number.isInteger(value)) {
|
|
415
|
+
issues.push({ path: path2, message: `must be an integer; received ${valueType(value)}` });
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
419
|
+
issues.push({ path: path2, message: `must be at least ${schema.minimum}` });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function defaultedConfig(input) {
|
|
424
|
+
return {
|
|
425
|
+
...input,
|
|
426
|
+
$schema: input.$schema === void 0 ? ARK_CONFIG_SCHEMA_URL : input.$schema,
|
|
427
|
+
schemaVersion: input.schemaVersion === void 0 ? ARK_CONFIG_SCHEMA_VERSION : input.schemaVersion,
|
|
428
|
+
include: input.include === void 0 ? ["src"] : input.include,
|
|
429
|
+
layers: input.layers === void 0 ? [] : input.layers,
|
|
430
|
+
rules: input.rules === void 0 ? DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule })) : input.rules
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function migrateArkConfig(input, source = "ark.config.json") {
|
|
434
|
+
if (!isObject(input)) {
|
|
435
|
+
throw new ArkConfigValidationError(source, [
|
|
436
|
+
{ path: "$", message: `must be an object; received ${valueType(input)}` }
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
const migratedFrom = input.schemaVersion === void 0 ? "unversioned" : null;
|
|
440
|
+
if (input.schemaVersion !== void 0 && input.schemaVersion !== ARK_CONFIG_SCHEMA_VERSION) {
|
|
441
|
+
throw new ArkConfigValidationError(source, [
|
|
442
|
+
{
|
|
443
|
+
path: "$.schemaVersion",
|
|
444
|
+
message: `unsupported version ${JSON.stringify(input.schemaVersion)}; expected ${ARK_CONFIG_SCHEMA_VERSION}`
|
|
445
|
+
}
|
|
446
|
+
]);
|
|
447
|
+
}
|
|
448
|
+
return { candidate: defaultedConfig(input), migratedFrom };
|
|
449
|
+
}
|
|
450
|
+
function loadArkConfigContract(input, source = "ark.config.json") {
|
|
451
|
+
const { candidate, migratedFrom } = migrateArkConfig(input, source);
|
|
452
|
+
const issues = [];
|
|
453
|
+
validateNode(
|
|
454
|
+
candidate,
|
|
455
|
+
ARK_CONFIG_SCHEMA,
|
|
456
|
+
"$",
|
|
457
|
+
ARK_CONFIG_SCHEMA,
|
|
458
|
+
issues
|
|
459
|
+
);
|
|
460
|
+
if (issues.length > 0) throw new ArkConfigValidationError(source, issues);
|
|
461
|
+
return { config: candidate, migratedFrom };
|
|
462
|
+
}
|
|
463
|
+
function parseArkConfigJson(json, source = "ark.config.json") {
|
|
464
|
+
let input;
|
|
465
|
+
try {
|
|
466
|
+
input = JSON.parse(json);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
throw new ArkConfigValidationError(source, [
|
|
469
|
+
{
|
|
470
|
+
path: "$",
|
|
471
|
+
message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
472
|
+
}
|
|
473
|
+
]);
|
|
474
|
+
}
|
|
475
|
+
return loadArkConfigContract(input, source);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/domain/adapterContract.ts
|
|
479
|
+
function text(value) {
|
|
480
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
481
|
+
}
|
|
482
|
+
function positiveInteger(value, fallback) {
|
|
483
|
+
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
484
|
+
}
|
|
485
|
+
function toAdapterDiagnostic(violation, fallbackSeverity = "error") {
|
|
486
|
+
const ruleId = text(violation.ruleId) ?? text(violation.code) ?? "ARK_UNKNOWN";
|
|
487
|
+
const severity = violation.severity === "warning" ? "warning" : fallbackSeverity;
|
|
488
|
+
const evidence = {
|
|
489
|
+
...text(violation.target) ? { target: text(violation.target) } : {},
|
|
490
|
+
...text(violation.fromLayer) ? { fromLayer: text(violation.fromLayer) } : {},
|
|
491
|
+
...text(violation.toLayer) ? { toLayer: text(violation.toLayer) } : {},
|
|
492
|
+
...typeof violation.typeOnly === "boolean" ? { typeOnly: violation.typeOnly } : {}
|
|
493
|
+
};
|
|
494
|
+
return {
|
|
495
|
+
ruleId,
|
|
496
|
+
severity,
|
|
497
|
+
message: text(violation.message) ?? ruleId,
|
|
498
|
+
location: {
|
|
499
|
+
file: text(violation.file) ?? "<unknown>",
|
|
500
|
+
line: positiveInteger(violation.line, 1),
|
|
501
|
+
column: positiveInteger(violation.column, 1)
|
|
502
|
+
},
|
|
503
|
+
evidence
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/domain/sourcePolicy.ts
|
|
508
|
+
var SOURCE_POLICY_MESSAGES = {
|
|
509
|
+
RAW_EVENT_PUBLISH: "Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",
|
|
510
|
+
PUBLISH_MISSING_SOURCE: "Strict Ark publish calls must include metadata.source."
|
|
511
|
+
};
|
|
512
|
+
function looksLikeArkIntent(value) {
|
|
513
|
+
return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(
|
|
514
|
+
value
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
function classifyPublishFacts(facts) {
|
|
518
|
+
if (!facts.publishCall) return [];
|
|
519
|
+
const findings = [];
|
|
520
|
+
if (facts.rawIntentName !== void 0 && looksLikeArkIntent(facts.rawIntentName) || facts.objectHasIntent) {
|
|
521
|
+
findings.push({
|
|
522
|
+
ruleId: "RAW_EVENT_PUBLISH",
|
|
523
|
+
message: SOURCE_POLICY_MESSAGES.RAW_EVENT_PUBLISH
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (facts.arkPublishCandidate && !facts.hasSource) {
|
|
527
|
+
findings.push({
|
|
528
|
+
ruleId: "PUBLISH_MISSING_SOURCE",
|
|
529
|
+
message: SOURCE_POLICY_MESSAGES.PUBLISH_MISSING_SOURCE
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
return findings;
|
|
533
|
+
}
|
|
534
|
+
|
|
173
535
|
// src/eslint/index.ts
|
|
174
536
|
function lintedFilename(context) {
|
|
175
537
|
if (typeof context.physicalFilename === "string" && context.physicalFilename.length > 0) {
|
|
@@ -187,6 +549,15 @@ function lintedFilename(context) {
|
|
|
187
549
|
}
|
|
188
550
|
return "";
|
|
189
551
|
}
|
|
552
|
+
function reportAdapterDiagnostic(context, node, messageId, violation, data) {
|
|
553
|
+
const diagnostic = toAdapterDiagnostic({
|
|
554
|
+
...violation,
|
|
555
|
+
line: violation.line ?? node.loc?.start?.line,
|
|
556
|
+
column: violation.column ?? (typeof node.loc?.start?.column === "number" ? node.loc.start.column + 1 : void 0)
|
|
557
|
+
});
|
|
558
|
+
context.report({ node, messageId, ...data ? { data } : {}, diagnostic });
|
|
559
|
+
return diagnostic;
|
|
560
|
+
}
|
|
190
561
|
function findConfigPath(startFile) {
|
|
191
562
|
if (!startFile || startFile === "<input>" || startFile.startsWith("stdin")) return null;
|
|
192
563
|
let dir = path.dirname(path.resolve(startFile));
|
|
@@ -201,14 +572,10 @@ function findConfigPath(startFile) {
|
|
|
201
572
|
var _configCache = /* @__PURE__ */ new Map();
|
|
202
573
|
function loadArkConfig(configPath) {
|
|
203
574
|
if (_configCache.has(configPath)) return _configCache.get(configPath) ?? null;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} catch {
|
|
209
|
-
_configCache.set(configPath, null);
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
575
|
+
if (!fs.existsSync(configPath)) return null;
|
|
576
|
+
const config = parseArkConfigJson(fs.readFileSync(configPath, "utf8"), configPath).config;
|
|
577
|
+
_configCache.set(configPath, config);
|
|
578
|
+
return config;
|
|
212
579
|
}
|
|
213
580
|
function resolveRelativeImport(fromFile, specifier) {
|
|
214
581
|
if (!specifier.startsWith(".")) return null;
|
|
@@ -239,6 +606,46 @@ function stringValue(node) {
|
|
|
239
606
|
function propertyName(node) {
|
|
240
607
|
return node?.name ?? stringValue(node);
|
|
241
608
|
}
|
|
609
|
+
function sourceCodeFor(context) {
|
|
610
|
+
return context.sourceCode ?? context.getSourceCode?.();
|
|
611
|
+
}
|
|
612
|
+
function referenceFor(context, node) {
|
|
613
|
+
let scope = sourceCodeFor(context)?.getScope?.(node);
|
|
614
|
+
while (scope) {
|
|
615
|
+
const reference = scope.references?.find((candidate) => candidate.identifier === node);
|
|
616
|
+
if (reference) return reference;
|
|
617
|
+
scope = scope.upper ?? void 0;
|
|
618
|
+
}
|
|
619
|
+
return void 0;
|
|
620
|
+
}
|
|
621
|
+
function isLocallyBound(context, node, name) {
|
|
622
|
+
const reference = referenceFor(context, node);
|
|
623
|
+
if (reference?.resolved) return (reference.resolved.defs?.length ?? 0) > 0;
|
|
624
|
+
let scope = sourceCodeFor(context)?.getScope?.(node);
|
|
625
|
+
while (scope) {
|
|
626
|
+
const variable = scope.set?.get(name);
|
|
627
|
+
if (variable) return (variable.defs?.length ?? 0) > 0;
|
|
628
|
+
scope = scope.upper ?? void 0;
|
|
629
|
+
}
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
function isValueIdentifierReference(context, node) {
|
|
633
|
+
const reference = referenceFor(context, node);
|
|
634
|
+
if (reference) return reference.isValueReference !== false;
|
|
635
|
+
return node.parent?.type === "VariableDeclarator" && node.parent.init === node;
|
|
636
|
+
}
|
|
637
|
+
function memberExpressionPath(node) {
|
|
638
|
+
if (node?.type === "Identifier" && node.name) {
|
|
639
|
+
return { root: node, segments: [node.name] };
|
|
640
|
+
}
|
|
641
|
+
if (!node) return void 0;
|
|
642
|
+
const memberLike = node.type === "MemberExpression" || Boolean(node.object && node.property);
|
|
643
|
+
if (!memberLike || node.computed === true) return void 0;
|
|
644
|
+
const base = memberExpressionPath(node.object);
|
|
645
|
+
const property = propertyName(node.property);
|
|
646
|
+
if (!base || !property) return void 0;
|
|
647
|
+
return { root: base.root, segments: [...base.segments, property] };
|
|
648
|
+
}
|
|
242
649
|
function calleePropertyName(node) {
|
|
243
650
|
return propertyName(node.callee?.property);
|
|
244
651
|
}
|
|
@@ -252,38 +659,14 @@ function objectHasMetadataSource(node) {
|
|
|
252
659
|
const metadata = objectProperty(node, "metadata")?.value;
|
|
253
660
|
return objectHasProperty(metadata, "source");
|
|
254
661
|
}
|
|
255
|
-
function looksLikeIntent(value) {
|
|
256
|
-
return /^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(
|
|
257
|
-
value
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
662
|
function isPublishCall(node) {
|
|
261
663
|
return calleePropertyName(node) === "publish";
|
|
262
664
|
}
|
|
263
|
-
function isDomainFileHeuristic(filename) {
|
|
264
|
-
const normalized = filename.split("\\").join("/").toLowerCase();
|
|
265
|
-
return normalized.includes("/domain/") || normalized.endsWith("/domain.ts");
|
|
266
|
-
}
|
|
267
|
-
function isInfraImportHeuristic(specifier) {
|
|
268
|
-
const normalized = specifier.toLowerCase();
|
|
269
|
-
return [
|
|
270
|
-
"adapter",
|
|
271
|
-
"adapters",
|
|
272
|
-
"infrastructure",
|
|
273
|
-
"persistence",
|
|
274
|
-
"repository",
|
|
275
|
-
"repositories",
|
|
276
|
-
"integration",
|
|
277
|
-
"database",
|
|
278
|
-
"db"
|
|
279
|
-
].some((token) => normalized.includes(token));
|
|
280
|
-
}
|
|
281
|
-
var DEFAULT_FORBIDDEN_GLOBALS = ["fetch", "process", "Date.now", "Math.random"];
|
|
282
665
|
var noDomainInfraImports = {
|
|
283
666
|
meta: {
|
|
284
667
|
type: "problem",
|
|
285
668
|
docs: {
|
|
286
|
-
description: "Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check).
|
|
669
|
+
description: "Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."
|
|
287
670
|
},
|
|
288
671
|
messages: {
|
|
289
672
|
forbiddenImport: "Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",
|
|
@@ -315,17 +698,24 @@ var noDomainInfraImports = {
|
|
|
315
698
|
toPath: relTarget,
|
|
316
699
|
layers: config.layers
|
|
317
700
|
})) {
|
|
318
|
-
|
|
701
|
+
reportAdapterDiagnostic(
|
|
702
|
+
context,
|
|
319
703
|
node,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
704
|
+
"forbiddenImport",
|
|
705
|
+
{
|
|
706
|
+
ruleId: "LAYER_IMPORT_VIOLATION",
|
|
707
|
+
file: relFile,
|
|
708
|
+
fromLayer,
|
|
709
|
+
toLayer,
|
|
710
|
+
target: relTarget,
|
|
711
|
+
...node.importKind === "type" ? { typeOnly: true } : {},
|
|
712
|
+
message: `${fromLayer} must not import ${toLayer}.`
|
|
713
|
+
},
|
|
714
|
+
{ fromLayer, toLayer, specifier: source }
|
|
715
|
+
);
|
|
323
716
|
}
|
|
324
717
|
return;
|
|
325
718
|
}
|
|
326
|
-
if (isDomainFileHeuristic(filename) && isInfraImportHeuristic(source)) {
|
|
327
|
-
context.report({ node, messageId: "forbiddenImportHeuristic" });
|
|
328
|
-
}
|
|
329
719
|
};
|
|
330
720
|
return {
|
|
331
721
|
ImportDeclaration: check,
|
|
@@ -348,11 +738,21 @@ var noRawEventPublish = {
|
|
|
348
738
|
create(context) {
|
|
349
739
|
return {
|
|
350
740
|
CallExpression(node) {
|
|
351
|
-
if (!isPublishCall(node)) return;
|
|
352
741
|
const firstArg = node.arguments?.[0];
|
|
353
742
|
const firstValue = stringValue(firstArg);
|
|
354
|
-
|
|
355
|
-
|
|
743
|
+
const findings = classifyPublishFacts({
|
|
744
|
+
publishCall: isPublishCall(node),
|
|
745
|
+
rawIntentName: firstValue,
|
|
746
|
+
objectHasIntent: objectHasProperty(firstArg, "intent"),
|
|
747
|
+
arkPublishCandidate: false,
|
|
748
|
+
hasSource: true
|
|
749
|
+
});
|
|
750
|
+
if (findings.some((finding) => finding.ruleId === "RAW_EVENT_PUBLISH")) {
|
|
751
|
+
const finding = findings.find((item) => item.ruleId === "RAW_EVENT_PUBLISH");
|
|
752
|
+
reportAdapterDiagnostic(context, node, "rawPublish", {
|
|
753
|
+
...finding,
|
|
754
|
+
file: lintedFilename(context)
|
|
755
|
+
});
|
|
356
756
|
}
|
|
357
757
|
}
|
|
358
758
|
};
|
|
@@ -372,13 +772,22 @@ var requirePublishSource = {
|
|
|
372
772
|
create(context) {
|
|
373
773
|
return {
|
|
374
774
|
CallExpression(node) {
|
|
375
|
-
if (!isPublishCall(node)) return;
|
|
376
775
|
const firstArg = node.arguments?.[0];
|
|
377
776
|
const metadataArg = node.arguments?.[2];
|
|
378
|
-
|
|
379
|
-
|
|
777
|
+
const findings = classifyPublishFacts({
|
|
778
|
+
publishCall: isPublishCall(node),
|
|
779
|
+
rawIntentName: stringValue(firstArg),
|
|
780
|
+
objectHasIntent: objectHasProperty(firstArg, "intent"),
|
|
781
|
+
arkPublishCandidate: true,
|
|
782
|
+
hasSource: objectHasMetadataSource(firstArg) || objectHasProperty(metadataArg, "source")
|
|
783
|
+
});
|
|
784
|
+
const finding = findings.find((item) => item.ruleId === "PUBLISH_MISSING_SOURCE");
|
|
785
|
+
if (finding) {
|
|
786
|
+
reportAdapterDiagnostic(context, node, "missingSource", {
|
|
787
|
+
...finding,
|
|
788
|
+
file: lintedFilename(context)
|
|
789
|
+
});
|
|
380
790
|
}
|
|
381
|
-
context.report({ node, messageId: "missingSource" });
|
|
382
791
|
}
|
|
383
792
|
};
|
|
384
793
|
}
|
|
@@ -387,7 +796,7 @@ var noForbiddenGlobals = {
|
|
|
387
796
|
meta: {
|
|
388
797
|
type: "problem",
|
|
389
798
|
docs: {
|
|
390
|
-
description: "Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as
|
|
799
|
+
description: "Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` explicitly overrides."
|
|
391
800
|
},
|
|
392
801
|
messages: {
|
|
393
802
|
forbiddenGlobal: 'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',
|
|
@@ -425,32 +834,63 @@ var noForbiddenGlobals = {
|
|
|
425
834
|
} else {
|
|
426
835
|
globals = null;
|
|
427
836
|
}
|
|
428
|
-
} else if (isDomainFileHeuristic(filename)) {
|
|
429
|
-
globals = new Set(DEFAULT_FORBIDDEN_GLOBALS);
|
|
430
837
|
}
|
|
431
838
|
if (!globals) {
|
|
432
839
|
return {};
|
|
433
840
|
}
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
841
|
+
const scopeAware = typeof sourceCodeFor(context)?.getScope === "function";
|
|
842
|
+
const report = (node, name) => {
|
|
843
|
+
const absFile = path.isAbsolute(filename) ? filename : path.resolve(filename);
|
|
844
|
+
const reportFile = root ? path.relative(root, absFile).split(path.sep).join("/") : filename;
|
|
845
|
+
reportAdapterDiagnostic(
|
|
846
|
+
context,
|
|
847
|
+
node,
|
|
848
|
+
config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
|
|
849
|
+
{
|
|
850
|
+
ruleId: "FORBIDDEN_GLOBAL",
|
|
851
|
+
file: reportFile,
|
|
852
|
+
fromLayer: layerName,
|
|
853
|
+
target: name,
|
|
854
|
+
message: `${layerName} must not use the ambient global "${name}".`
|
|
855
|
+
},
|
|
856
|
+
{ name, layer: layerName }
|
|
857
|
+
);
|
|
858
|
+
};
|
|
439
859
|
return {
|
|
440
860
|
MemberExpression(node) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
861
|
+
if (node.parent?.type === "MemberExpression" && node.parent.object === node) return;
|
|
862
|
+
const path2 = memberExpressionPath(node);
|
|
863
|
+
if (!path2 || isLocallyBound(context, path2.root, path2.segments[0])) return;
|
|
864
|
+
const explicitGlobalThis = path2.segments[0] === "globalThis";
|
|
865
|
+
const normalized = explicitGlobalThis ? path2.segments.slice(1) : path2.segments;
|
|
866
|
+
let match;
|
|
867
|
+
for (let length = normalized.length; length >= (explicitGlobalThis ? 1 : 2); length -= 1) {
|
|
868
|
+
const candidate = normalized.slice(0, length).join(".");
|
|
869
|
+
if (globals.has(candidate)) {
|
|
870
|
+
match = candidate;
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (match) report(node, match);
|
|
875
|
+
else if (!scopeAware && globals.has(path2.segments[0])) {
|
|
876
|
+
report(node, path2.segments[0]);
|
|
877
|
+
}
|
|
446
878
|
},
|
|
447
879
|
CallExpression(node) {
|
|
880
|
+
if (scopeAware) return;
|
|
448
881
|
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
449
882
|
if (callee && globals.has(callee)) report(node, callee);
|
|
450
883
|
},
|
|
451
884
|
NewExpression(node) {
|
|
885
|
+
if (scopeAware) return;
|
|
452
886
|
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
453
887
|
if (callee && globals.has(callee)) report(node, callee);
|
|
888
|
+
},
|
|
889
|
+
Identifier(node) {
|
|
890
|
+
if (!scopeAware || !node.name || !globals.has(node.name) || !isValueIdentifierReference(context, node) || isLocallyBound(context, node, node.name)) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
report(node, node.name);
|
|
454
894
|
}
|
|
455
895
|
};
|
|
456
896
|
}
|
|
@@ -489,4 +929,3 @@ export {
|
|
|
489
929
|
requirePublishSource,
|
|
490
930
|
resolveRelativeImport
|
|
491
931
|
};
|
|
492
|
-
//# sourceMappingURL=index.js.map
|