arkgate 3.3.0 → 3.5.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 +75 -0
- package/README.md +4 -3
- package/bin/ark-check.mjs +3 -1
- package/bin/ark-mcp.mjs +10 -0
- package/bin/lib/adapter-contract.mjs +3 -0
- package/bin/lib/ambient-state.mjs +221 -0
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/architecture-scan.mjs +31 -0
- package/bin/lib/config-contract.mjs +25 -0
- package/bin/lib/contract-smells.mjs +154 -18
- package/bin/lib/doctor-advisories.mjs +20 -0
- package/bin/lib/doctor-plan.mjs +5 -5
- package/bin/lib/html-report-advisories.mjs +149 -0
- package/bin/lib/html-report.mjs +6 -0
- package/bin/lib/remediation.mjs +14 -0
- package/bin/lib/ts-resolve.mjs +2 -1
- package/bin/lib/violations.mjs +2 -0
- package/dist/{configTypes-CVWWhBoe.d.cts → configTypes-DAPvBqK6.d.cts} +7 -0
- package/dist/{configTypes-CVWWhBoe.d.ts → configTypes-DAPvBqK6.d.ts} +7 -0
- package/dist/eslint/index.cjs +2 -2
- package/dist/eslint/index.d.cts +3 -2
- package/dist/eslint/index.d.ts +3 -2
- package/dist/eslint/index.js +2 -2
- package/dist/index.cjs +7 -7
- package/dist/index.d.cts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +6 -6
- package/docs/agent-guide.md +21 -5
- package/docs/configuration.md +3 -0
- package/docs/package-surface.md +7 -3
- package/package.json +3 -2
- package/schemas/ark.config.schema.json +25 -0
- package/server.json +2 -2
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor's advisory sensors, aggregated (W01 contract health + U05 ambient
|
|
3
|
+
* state). Advisory only: nothing here feeds a verdict, designFitness, or an
|
|
4
|
+
* exit code. One seam keeps doctor-plan.mjs inside its module budget as new
|
|
5
|
+
* advisory surfaces land.
|
|
6
|
+
*/
|
|
7
|
+
import { computeAmbientState, printAmbientStateSection } from './ambient-state.mjs';
|
|
8
|
+
import { computeContractHealth, printContractHealthSection } from './contract-smells.mjs';
|
|
9
|
+
|
|
10
|
+
export function computeDoctorAdvisories(root, config, cov, rules, files, ts) {
|
|
11
|
+
return {
|
|
12
|
+
contractHealth: computeContractHealth(root, config, cov, rules),
|
|
13
|
+
ambientState: computeAmbientState(ts, root, config, files),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function printDoctorAdvisories(advisories, io) {
|
|
18
|
+
printContractHealthSection(advisories.contractHealth, io);
|
|
19
|
+
printAmbientStateSection(advisories.ambientState, io);
|
|
20
|
+
}
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
} from './post-green-path.mjs';
|
|
41
41
|
import { loadGoldenPattern, summarizeGoldenPattern } from './golden-pattern.mjs';
|
|
42
42
|
import { summarizePilotLoop } from './pilot-loop.mjs';
|
|
43
|
-
import {
|
|
43
|
+
import { computeDoctorAdvisories, printDoctorAdvisories } from './doctor-advisories.mjs';
|
|
44
44
|
|
|
45
45
|
const color = {
|
|
46
46
|
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
@@ -425,8 +425,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
425
425
|
patternBets: patternBetsForLoop,
|
|
426
426
|
designSmells,
|
|
427
427
|
});
|
|
428
|
-
|
|
429
|
-
const contractHealth = computeContractHealth(root, config, cov, rules);
|
|
428
|
+
const { contractHealth, ambientState } = computeDoctorAdvisories(root, config, cov, rules, files, options.ts); // W01+U05 advisories — never a verdict
|
|
430
429
|
|
|
431
430
|
if (asJson) {
|
|
432
431
|
console.log(
|
|
@@ -465,6 +464,8 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
465
464
|
pilotLoop,
|
|
466
465
|
// W01: contract-health meta-lint (advisory; verdict unchanged).
|
|
467
466
|
contractHealth,
|
|
467
|
+
// U05: ambient-state sensor (advisory; opt-in; verdict unchanged).
|
|
468
|
+
ambientState,
|
|
468
469
|
governed: cov.governed,
|
|
469
470
|
emptyLayers: cov.emptyLayers,
|
|
470
471
|
layersWithoutRules: cov.layersWithoutRules,
|
|
@@ -646,8 +647,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
646
647
|
);
|
|
647
648
|
}
|
|
648
649
|
|
|
649
|
-
|
|
650
|
-
printContractHealthSection(contractHealth, { line, warn, color });
|
|
650
|
+
printDoctorAdvisories({ contractHealth, ambientState }, { line, warn, color }); // advisory sections
|
|
651
651
|
|
|
652
652
|
console.log('');
|
|
653
653
|
console.log(color.bold('Coverage'));
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X01 — advisory sections for the HTML report (report parity with doctor).
|
|
3
|
+
*
|
|
4
|
+
* The report is a RENDERING of doctor truth: every advisory surface the doctor
|
|
5
|
+
* emits must have a section here, marked with `data-advisory="<key>"`. The
|
|
6
|
+
* parity guard (reportParity.test.ts) enumerates the doctor's advisory keys
|
|
7
|
+
* and fails when one has no section — that is the standing rule that keeps
|
|
8
|
+
* this report from falling behind the product again.
|
|
9
|
+
*/
|
|
10
|
+
import { effectiveCapabilityDeny } from './analysis-engine.mjs';
|
|
11
|
+
|
|
12
|
+
// htmlEscape is injected by the caller (html-report.mjs) — importing it back
|
|
13
|
+
// would be a dependency cycle, and the repo's own gate blocks that. The
|
|
14
|
+
// fallback still escapes so a caller that forgets to inject cannot ship XSS.
|
|
15
|
+
let esc = (v) =>
|
|
16
|
+
String(v).replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
17
|
+
|
|
18
|
+
/** Layer badges for the layers table: purity walls next to forbidden globals. */
|
|
19
|
+
export function capabilityBadgesFor(layer, escape = esc) {
|
|
20
|
+
const previous = esc;
|
|
21
|
+
esc = escape;
|
|
22
|
+
try {
|
|
23
|
+
return badgesInner(layer);
|
|
24
|
+
} finally {
|
|
25
|
+
esc = previous;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function badgesInner(layer) {
|
|
30
|
+
const deny = effectiveCapabilityDeny(layer ?? {});
|
|
31
|
+
if (deny.length === 0) return '';
|
|
32
|
+
if (layer?.pure === true) {
|
|
33
|
+
return '<span class="tag warn" title="pure: true — all seven effect capabilities denied (ADR 0009)">pure</span>';
|
|
34
|
+
}
|
|
35
|
+
return `<span class="tag warn" title="capabilities.deny (ADR 0009)">walls: ${deny.map(esc).join(', ')}</span>`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function governanceWeightHtml(gw) {
|
|
39
|
+
if (!gw || gw.weight === 'unknown') {
|
|
40
|
+
return '<p class="muted">Governance weight: unknown (no governed files or layers).</p>';
|
|
41
|
+
}
|
|
42
|
+
const label = `${gw.weight} — ${gw.declaredLayers} layer(s), ${gw.rules} rule(s), ${gw.governedFiles} governed file(s)` +
|
|
43
|
+
(gw.filesPerLayer != null ? ` (${gw.filesPerLayer} files/layer · ${gw.rulesPerLayer} rules/layer)` : '');
|
|
44
|
+
return `
|
|
45
|
+
<div data-advisory="governanceWeight">
|
|
46
|
+
<p><b>Governance weight:</b> <span class="tag ${gw.weight === 'typical' ? 'ok' : 'warn'}">${esc(String(gw.weight))}</span> ${esc(label)}</p>
|
|
47
|
+
<p class="muted">${esc(gw.note ?? '')} Facts, never a score or gate input (<code>notAScore</code>).</p>
|
|
48
|
+
</div>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ackLifecycleHtml(lc) {
|
|
52
|
+
if (!lc) return '';
|
|
53
|
+
const rows = [];
|
|
54
|
+
if ((lc.expiredCount ?? 0) > 0) {
|
|
55
|
+
const edges = (lc.expired ?? [])
|
|
56
|
+
.map((e) => `<code>${esc(e.edge)}</code> (review-by ${esc(e.reviewBy)})`)
|
|
57
|
+
.join(' · ');
|
|
58
|
+
rows.push(
|
|
59
|
+
`<p><span class="tag warn">expired</span> ${lc.expiredCount} acknowledgment(s) past review-by — no longer applied, the smell is active again: ${edges}</p>`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if ((lc.malformed ?? 0) > 0) {
|
|
63
|
+
rows.push(
|
|
64
|
+
`<p><span class="tag warn">malformed</span> ${lc.malformed} acknowledgment(s) have a malformed review-by (expected YYYY-MM-DD) — ignored, not silently applied.</p>`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if ((lc.undated ?? 0) > 0) {
|
|
68
|
+
rows.push(
|
|
69
|
+
`<p class="muted">${lc.undated} applied acknowledgment(s) have no review-by date — add one so migration acks cannot fossilize.</p>`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return rows.join('\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function contractHealthHtml(health) {
|
|
76
|
+
if (!health) return '';
|
|
77
|
+
const smells = Array.isArray(health.smells) ? health.smells : [];
|
|
78
|
+
const acked = health.acknowledged ?? 0;
|
|
79
|
+
const ackNote = acked > 0
|
|
80
|
+
? `<p class="muted">Acknowledged edges applied: <b>${acked}</b> (${esc(health.ackFile?.path ?? '.ark/contract-smell-acks.json')}) — deliberate loops recorded with a reason; review them when their migrations finish.</p>`
|
|
81
|
+
: '';
|
|
82
|
+
const invalid = health.ackFile?.invalid
|
|
83
|
+
? `<p class="tag warn">Acknowledgment sidecar present but invalid — acks are ignored, never silently applied.</p>`
|
|
84
|
+
: '';
|
|
85
|
+
const body = smells.length === 0
|
|
86
|
+
? `<p class="muted">No contract smells detected — no explicitly bidirectional allows, peripheral-into-core allows, lateral adapter allows, or dead rules beyond what is acknowledged.</p>`
|
|
87
|
+
: smells
|
|
88
|
+
.map(
|
|
89
|
+
(s) => `
|
|
90
|
+
<div class="finding">
|
|
91
|
+
<p><span class="tag warn">${esc(s.id)}</span> ${esc(s.outcome ?? s.message ?? '')}</p>
|
|
92
|
+
<p class="muted">${esc(s.message ?? '')}</p>
|
|
93
|
+
<p class="muted">evidence: <code>${(s.evidence ?? []).slice(0, 6).map(esc).join('</code> · <code>')}</code></p>
|
|
94
|
+
<p class="muted">fix: ${esc(s.fix ?? '')}</p>
|
|
95
|
+
</div>`
|
|
96
|
+
)
|
|
97
|
+
.join('\n');
|
|
98
|
+
return `
|
|
99
|
+
<section data-advisory="contractHealth">
|
|
100
|
+
<h2>Contract health <span class="muted">(advisory — meta-lint of the contract itself; never changes the verdict)</span></h2>
|
|
101
|
+
${invalid}
|
|
102
|
+
${body}
|
|
103
|
+
${ackNote}
|
|
104
|
+
${ackLifecycleHtml(health.ackLifecycle)}
|
|
105
|
+
${governanceWeightHtml(health.governanceWeight)}
|
|
106
|
+
</section>`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ambientStateHtml(state) {
|
|
110
|
+
if (!state) return '';
|
|
111
|
+
if (state.available === false) {
|
|
112
|
+
return `
|
|
113
|
+
<section data-advisory="ambientState">
|
|
114
|
+
<h2>Ambient state <span class="muted">(advisory)</span></h2>
|
|
115
|
+
<p class="muted">${esc(state.note ?? 'Sensor unavailable in this run.')}</p>
|
|
116
|
+
</section>`;
|
|
117
|
+
}
|
|
118
|
+
const findings = Array.isArray(state.findings) ? state.findings : [];
|
|
119
|
+
const body = !state.active
|
|
120
|
+
? '<p class="muted">Idle — no <code>pure: true</code> layer opted in. Declare a pure layer to scan module-scope mutable state.</p>'
|
|
121
|
+
: findings.length === 0
|
|
122
|
+
? '<p class="muted">Active and clean — no module-scope <code>let</code>/<code>var</code> in pure layers.</p>'
|
|
123
|
+
: `<ul>${findings
|
|
124
|
+
.slice(0, 10)
|
|
125
|
+
.map(
|
|
126
|
+
(f) => `<li><code>${esc(f.file)}:${f.line}</code> — <b>${esc(f.name)}</b> <span class="tag warn">${esc(f.kind)}</span></li>`
|
|
127
|
+
)
|
|
128
|
+
.join('')}</ul>` +
|
|
129
|
+
(state.findingCount > 10 ? `<p class="muted">…(+${state.findingCount - 10} more in doctor JSON)</p>` : '') +
|
|
130
|
+
(state.acknowledged > 0 ? `<p class="muted">acknowledged module state: ${state.acknowledged}</p>` : '');
|
|
131
|
+
return `
|
|
132
|
+
<section data-advisory="ambientState">
|
|
133
|
+
<h2>Ambient state <span class="muted">(advisory — opt-in via pure layers; no strict mode exists)</span></h2>
|
|
134
|
+
${body}
|
|
135
|
+
</section>`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Render every doctor advisory as report sections. Keys must cover everything
|
|
140
|
+
* `computeDoctorAdvisories` returns — the parity guard enforces it.
|
|
141
|
+
* @param escape injected HTML escaper (dependency points html-report → here only)
|
|
142
|
+
*/
|
|
143
|
+
export function renderAdvisorySections(advisories, escape) {
|
|
144
|
+
if (!advisories || typeof advisories !== 'object') return '';
|
|
145
|
+
if (typeof escape === 'function') esc = escape;
|
|
146
|
+
return [contractHealthHtml(advisories.contractHealth), ambientStateHtml(advisories.ambientState)]
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.join('\n');
|
|
149
|
+
}
|
package/bin/lib/html-report.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
renderWritePathAdoptionBlock,
|
|
20
20
|
} from './html-report-depth.mjs';
|
|
21
21
|
import { FIX_HINTS } from './violations.mjs';
|
|
22
|
+
import { capabilityBadgesFor, renderAdvisorySections } from './html-report-advisories.mjs';
|
|
22
23
|
|
|
23
24
|
export function detectEnforcement(root) {
|
|
24
25
|
const has = (rel) => fs.existsSync(path.join(root, rel));
|
|
@@ -455,6 +456,8 @@ export function renderHtmlReport({
|
|
|
455
456
|
adoption = null,
|
|
456
457
|
/** Optional design-depth (doctor parity): designFitness, designSmells, pilotLoop, postGreenPath, goldenPattern */
|
|
457
458
|
designDepth = null,
|
|
459
|
+
/** Doctor advisory parity (X01): contractHealth (+governanceWeight), ambientState — guarded by reportParity.test.ts */
|
|
460
|
+
advisories = null,
|
|
458
461
|
}) {
|
|
459
462
|
const layers = Array.isArray(config.layers) ? config.layers : [];
|
|
460
463
|
const rules = Array.isArray(config.rules) ? config.rules : [];
|
|
@@ -710,6 +713,7 @@ export function renderHtmlReport({
|
|
|
710
713
|
Array.isArray(layer.forbiddenGlobals) && layer.forbiddenGlobals.length
|
|
711
714
|
? `<span class="tag warn">no ${layer.forbiddenGlobals.map(esc).join(', ')}</span>`
|
|
712
715
|
: '',
|
|
716
|
+
capabilityBadgesFor(layer, esc),
|
|
713
717
|
layer.mayImportInfrastructure ? '<span class="tag">may import infra</span>' : '',
|
|
714
718
|
Array.isArray(layer.intentPrefixes) && layer.intentPrefixes.length
|
|
715
719
|
? `<span class="tag">${layer.intentPrefixes.map(esc).join(' ')}</span>`
|
|
@@ -1187,6 +1191,8 @@ export function renderHtmlReport({
|
|
|
1187
1191
|
${violationBlocks}
|
|
1188
1192
|
</div>
|
|
1189
1193
|
|
|
1194
|
+
${renderAdvisorySections(advisories, esc)}
|
|
1195
|
+
|
|
1190
1196
|
<div class="section card">
|
|
1191
1197
|
<h2>Enforcement points</h2>
|
|
1192
1198
|
<p class="dim" style="margin:.15rem 0 .85rem;font-size:.88rem">Write-time · merge-time · editor · ratchet. Same contract everywhere.</p>
|
package/bin/lib/remediation.mjs
CHANGED
|
@@ -54,6 +54,8 @@ export function deterministicNextAction(violation) {
|
|
|
54
54
|
return `Define a port in ${violation.fromLayer ?? 'the source layer'}, inject the ${violation.toLayer ?? 'outer-layer'} implementation, then preflight again.`;
|
|
55
55
|
case 'FORBIDDEN_GLOBAL':
|
|
56
56
|
return `Inject ${violation.target ?? 'the capability'} through a port, then preflight again.`;
|
|
57
|
+
case 'CAPABILITY_VIOLATION':
|
|
58
|
+
return `Define a ${String(violation.capability ?? 'capability')} port in ${violation.fromLayer ?? 'the walled layer'}, bind the implementation outside it, then preflight again.`;
|
|
57
59
|
case 'CIRCULAR_DEPENDENCY':
|
|
58
60
|
return 'Extract the shared dependency into a third module, then preflight again.';
|
|
59
61
|
case 'RAW_EVENT_PUBLISH':
|
|
@@ -148,6 +150,13 @@ export function classifyRemediation(violation) {
|
|
|
148
150
|
rationale: 'Ambient global in a pure layer: inject the capability through a port (Clock, Config, Http). Introducing the port is a design decision.',
|
|
149
151
|
};
|
|
150
152
|
}
|
|
153
|
+
if (ruleId === 'CAPABILITY_VIOLATION') {
|
|
154
|
+
return {
|
|
155
|
+
class: 'judgment',
|
|
156
|
+
confidence: 0.8,
|
|
157
|
+
rationale: 'A denied effect capability (clock/network/persistence/…) reached a walled layer: define a port and bind the implementation outside it. Never mechanical-safe — the port shape is a design decision.',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
151
160
|
if (ruleId === 'CIRCULAR_DEPENDENCY') {
|
|
152
161
|
return {
|
|
153
162
|
class: 'judgment',
|
|
@@ -201,6 +210,11 @@ export function enrichViolationWithFixClass(violation) {
|
|
|
201
210
|
enriched.effort = 'small';
|
|
202
211
|
enriched.enthusiastHint = `Do not call "${violation.target ?? 'that global'}" here. Pass the capability in through a small interface (for example a Clock, HttpPort, or Config provider).`;
|
|
203
212
|
break;
|
|
213
|
+
case 'CAPABILITY_VIOLATION':
|
|
214
|
+
enriched.fixClass = 'inject-port';
|
|
215
|
+
enriched.effort = 'medium';
|
|
216
|
+
enriched.enthusiastHint = `This layer denies the ${String(violation.capability ?? 'effect')} capability. Define a small port (for example ClockPort, HttpPort, StoragePort) and inject the implementation from an adapter layer instead of using "${violation.target ?? 'the capability'}" directly.`;
|
|
217
|
+
break;
|
|
204
218
|
case 'RAW_EVENT_PUBLISH':
|
|
205
219
|
enriched.fixClass = 'registered-intent';
|
|
206
220
|
enriched.effort = 'small';
|
package/bin/lib/ts-resolve.mjs
CHANGED
|
@@ -129,9 +129,10 @@ export function scanCacheKey(root, args) {
|
|
|
129
129
|
// v3: per-file exportsOnlyTypes. v4: typeOnlyExportNames + namedBindings.
|
|
130
130
|
// v5: hasTopLevelSideEffects. v6: non-exported impure inits + non-export class statics.
|
|
131
131
|
// v7: scope-aware forbidden globals + import-equals dependency edges.
|
|
132
|
+
// v8: opted-in capability walls (U04) — stale caches must not miss wall verdicts.
|
|
132
133
|
return crypto
|
|
133
134
|
.createHash('sha1')
|
|
134
|
-
.update(`ark-check-cache-
|
|
135
|
+
.update(`ark-check-cache-v8\0${read(configPath)}\0${manifestPath ? read(manifestPath) : ''}`)
|
|
135
136
|
.digest('hex');
|
|
136
137
|
}
|
|
137
138
|
|
package/bin/lib/violations.mjs
CHANGED
|
@@ -45,6 +45,8 @@ export const FIX_HINTS = {
|
|
|
45
45
|
'Use a source intent that belongs to the same layer as the publishing file, or move the file.',
|
|
46
46
|
FORBIDDEN_GLOBAL:
|
|
47
47
|
'Inject the capability through a port (e.g. a Clock, IdGenerator, or HttpPort) instead of reaching for the ambient global.',
|
|
48
|
+
CAPABILITY_VIOLATION:
|
|
49
|
+
'This layer denies that effect. Define a small port (ClockPort, HttpPort, StoragePort) and bind the implementation in an adapter layer.',
|
|
48
50
|
CIRCULAR_DEPENDENCY:
|
|
49
51
|
'Break the cycle: extract the shared code into a module both sides import, invert one edge behind a port/interface, or merge the files if they are really one unit.',
|
|
50
52
|
};
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
type ArkConfigSchemaVersion = '1.0';
|
|
10
10
|
type ArkConfigCyclePolicy = 'strict' | 'soft' | 'framework-soft' | 'off';
|
|
11
|
+
type ArkConfigLayerCapabilities = {
|
|
12
|
+
deny?: string[];
|
|
13
|
+
};
|
|
11
14
|
type ArkConfigLayer = {
|
|
12
15
|
name: string;
|
|
13
16
|
patterns: string[];
|
|
@@ -15,6 +18,10 @@ type ArkConfigLayer = {
|
|
|
15
18
|
intentPrefixes?: string[];
|
|
16
19
|
description?: string;
|
|
17
20
|
forbiddenGlobals?: string[];
|
|
21
|
+
/** ADR 0009 D2 — opt-in effect-capability walls; absence changes no verdict. */
|
|
22
|
+
capabilities?: ArkConfigLayerCapabilities;
|
|
23
|
+
/** Dual-depth sugar: `pure: true` denies all seven capabilities. */
|
|
24
|
+
pure?: boolean;
|
|
18
25
|
mayImportInfrastructure?: boolean;
|
|
19
26
|
optional?: boolean;
|
|
20
27
|
};
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
type ArkConfigSchemaVersion = '1.0';
|
|
10
10
|
type ArkConfigCyclePolicy = 'strict' | 'soft' | 'framework-soft' | 'off';
|
|
11
|
+
type ArkConfigLayerCapabilities = {
|
|
12
|
+
deny?: string[];
|
|
13
|
+
};
|
|
11
14
|
type ArkConfigLayer = {
|
|
12
15
|
name: string;
|
|
13
16
|
patterns: string[];
|
|
@@ -15,6 +18,10 @@ type ArkConfigLayer = {
|
|
|
15
18
|
intentPrefixes?: string[];
|
|
16
19
|
description?: string;
|
|
17
20
|
forbiddenGlobals?: string[];
|
|
21
|
+
/** ADR 0009 D2 — opt-in effect-capability walls; absence changes no verdict. */
|
|
22
|
+
capabilities?: ArkConfigLayerCapabilities;
|
|
23
|
+
/** Dual-depth sugar: `pure: true` denies all seven capabilities. */
|
|
24
|
+
pure?: boolean;
|
|
18
25
|
mayImportInfrastructure?: boolean;
|
|
19
26
|
optional?: boolean;
|
|
20
27
|
};
|
package/dist/eslint/index.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var be=Object.create;var C=Object.defineProperty;var he=Object.getOwnPropertyDescriptor;var Ae=Object.getOwnPropertyNames;var ke=Object.getPrototypeOf,Se=Object.prototype.hasOwnProperty;var Ie=(e,t)=>{for(var n in t)C(e,n,{get:t[n],enumerable:!0})},U=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Ae(t))!Se.call(e,r)&&r!==n&&C(e,r,{get:()=>t[r],enumerable:!(i=he(t,r))||i.enumerable});return e};var q=(e,t,n)=>(n=e!=null?be(ke(e)):{},U(t||!e||!e.__esModule?C(n,"default",{value:e,enumerable:!0}):n,e)),we=e=>U(C({},"__esModule",{value:!0}),e);var Be={};Ie(Be,{default:()=>Ge,findConfigPath:()=>L,globToRegExp:()=>R,isEdgeDenied:()=>F,layerForRelativePath:()=>h,loadArkConfig:()=>_,noDeniedCapabilities:()=>ye,noDomainInfraImports:()=>fe,noForbiddenGlobals:()=>me,noRawEventPublish:()=>pe,patternSpecificity:()=>j,plugin:()=>N,requirePublishSource:()=>ge,resolveRelativeImport:()=>ae});module.exports=we(Be);var S=q(require("fs"),1),u=q(require("path"),1);var W=new Map;function Y(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function P(e){let t="";for(let n=0;n<e.length;n+=1){let i=e[n];if(i==="\\"&&n+1<e.length){let r=e[n+1];if("*?{}[],".includes(r)||r==="\\"){t+="\\"+r,n+=1;continue}t+="/";continue}t+=i}return t}function Ce(e){let t=0;for(let n=0;n<e.length;n+=1){let i=e[n];if(i==="\\"){n+=1;continue}if(i==="{")t+=1;else if(i==="}"&&(t-=1,t<0))return!1}return t===0}function R(e){let t=W.get(e);if(t)return t;let n=P(e),i=Ce(n),r="",s=0;for(let c=0;c<n.length;c+=1){let p=n[c];p==="\\"&&c+1<n.length?(r+=Y(n[c+1]),c+=1):p==="*"?n[c+1]==="*"?n[c+2]==="/"?(r+="(?:.*/)?",c+=2):(r+=".*",c+=1):r+="[^/]*":p==="?"?r+="[^/]":p==="{"&&i?(r+="(?:",s+=1):p==="}"&&i&&s>0?(r+=")",s-=1):p===","&&i&&s>0?r+="|":r+=Y(p)}let l=new RegExp(`^${r}$`);return W.set(e,l),l}function j(e){let t=P(String(e)),i=t.split("*")[0].split("/").filter(Boolean).length,r=t.replace(/\*/g,"").length;return i*1e4+r}function h(e,t){let n=String(e).split(/[/\\]/).join("/"),i,r=-1;for(let s of t??[])if(!(s.exclude??[]).some(l=>R(l).test(n))){for(let l of s.patterns??[])if(R(l).test(n)){let c=j(l);c>r&&(r=c,i=s.name)}}return i}function z(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),i=new Set(t.map(r=>String(r).toLowerCase()));for(let r=0;r<n.length-1;r+=1)if(i.has(n[r].toLowerCase()))return`${n[r]}/${n[r+1]}`}function Re(e){let t=new Set;for(let n of e??[]){let r=P(String(n)).split("/").filter(Boolean);for(let s=0;s<r.length;s+=1){let l=r[s];if((l==="**"||l==="*")&&s>0){let c=r[s-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function xe(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(r=>typeof r=="string"&&r.length>0);let i=(n??[]).find(r=>r.name===t);return Re(i?.patterns)}function Ee(e,t,n,i){for(let r of e??[])if(!(r.from!==t||r.to!==n)&&r.allowed===!1){if(r.peerIsolation){let s=i?.fromPath,l=i?.toPath;if(!s||!l)continue;let c=xe(r,t,i?.layers);if(c.length===0)continue;let p=z(s,c),g=z(l,c);if(!p||!g)continue;if(p!==g)return r;continue}if(t!==n)return r}}function F(e,t,n,i){return Ee(e,t,n,i)!==void 0}var J=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),Ne=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),Ue=Object.freeze(Object.keys(Ne).sort()),V=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function Z(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=V[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let i=e.slice(0,n),r=V[i];if(r)return r;let s=e.indexOf("/",n+1);return s<0?null:V[e.slice(0,s)]??null}function X(e){if(e?.pure===!0)return[...J].sort();let n=(e?.capabilities?.deny??[]).filter(i=>J.includes(i));return[...new Set(n)].sort()}var M="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",Q=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],Le=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function _e(){let e=[];for(let t of Q)for(let n of Q)t===n||Le.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var te=_e();var b={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},ee={$schema:"https://json-schema.org/draft/2020-12/schema",$id:M,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:M,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.0",default:"1.0"},name:{type:"string",minLength:1},include:{...b,minItems:1,default:["src"]},exclude:{...b,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:te,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...b,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...b,minItems:1},exclude:b,intentPrefixes:b,description:{type:"string",minLength:1},forbiddenGlobals:b,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...b,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},k=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}):
|
|
2
2
|
${n.map(i=>`- ${i.path}: ${i.message}`).join(`
|
|
3
|
-
`)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function Z(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function j(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function h(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Ee(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function I(e,t,n,i,r){if(t.$ref){let o=Ee(t.$ref,i);if(!o){r.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}I(e,o,n,i,r);return}if(t.const!==void 0&&!Object.is(e,t.const)){r.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(o=>Object.is(o,e))){r.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!Z(e)){r.push({path:n,message:`must be an object; received ${h(e)}`});return}let o=t.properties??{};for(let s of t.required??[])e[s]===void 0&&r.push({path:j(n,s),message:"is required"});if(t.additionalProperties===!1)for(let s of Object.keys(e))s in o||r.push({path:j(n,s),message:"unknown field"});for(let[s,a]of Object.entries(o))e[s]!==void 0&&I(e[s],a,j(n,s),i,r);return}if(t.type==="array"){if(!Array.isArray(e)){r.push({path:n,message:`must be an array; received ${h(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&r.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let o=e.map(s=>JSON.stringify(s));new Set(o).size!==o.length&&r.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((o,s)=>I(o,t.items,`${n}[${s}]`,i,r));return}if(t.type==="string"){if(typeof e!="string"){r.push({path:n,message:`must be a string; received ${h(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&r.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&r.push({path:n,message:`must be a boolean; received ${h(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){r.push({path:n,message:`must be an integer; received ${h(e)}`});return}t.minimum!==void 0&&e<t.minimum&&r.push({path:n,message:`must be at least ${t.minimum}`})}}function Ne(e){return{...e,$schema:e.$schema===void 0?V:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?z.map(t=>({...t})):e.rules}}function xe(e,t="ark.config.json"){if(!Z(e))throw new A(t,[{path:"$",message:`must be an object; received ${h(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new A(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:Ne(e),migratedFrom:n}}function we(e,t="ark.config.json"){let{candidate:n,migratedFrom:i}=xe(e,t),r=[];if(I(n,Y,"$",Y,r),r.length>0)throw new A(t,r);return{config:n,migratedFrom:i}}function Q(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(i){throw new A(t,[{path:"$",message:`invalid JSON: ${i instanceof Error?i.message:String(i)}`}])}return we(n,t)}function m(e){return typeof e=="string"&&e.length>0?e:void 0}function X(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function Le(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function ee(e,t="error"){let n=m(e.ruleId)??m(e.code)??"ARK_UNKNOWN",i=e.severity==="warning"?"warning":t,r={...m(e.target)?{target:m(e.target)}:{},...m(e.fromLayer)?{fromLayer:m(e.fromLayer)}:{},...m(e.toLayer)?{toLayer:m(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:i,message:m(e.message)??n,location:{file:m(e.file)??"<unknown>",line:X(e.line,1),column:X(e.column,1)},evidence:r,nextAction:m(e.nextAction)??Le(n,r,e)}}var te={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function _e(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function F(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&_e(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:te.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:te.PUBLISH_MISSING_SOURCE}),t}function N(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function x(e,t,n,i,r){let o=ee({...i,line:i.line??t.loc?.start?.line,column:i.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...r?{data:r}:{},diagnostic:o}),o}function D(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=c.default.dirname(c.default.resolve(e));for(;;){let n=c.default.join(t,"ark.config.json");if(b.default.existsSync(n))return n;let i=c.default.dirname(t);if(i===t)return null;t=i}}var M=new Map;function G(e){if(M.has(e))return M.get(e)??null;if(!b.default.existsSync(e))return null;let t=Q(b.default.readFileSync(e,"utf8"),e).config;return M.set(e,t),t}function re(e,t){if(!t.startsWith("."))return null;let n=c.default.resolve(c.default.dirname(e),t),i=[n,`${n}.ts`,`${n}.tsx`,`${n}.mts`,`${n}.cts`,`${n}.js`,`${n}.jsx`,c.default.join(n,"index.ts"),c.default.join(n,"index.tsx"),c.default.join(n,"index.js")];for(let r of i)try{if(b.default.existsSync(r)&&b.default.statSync(r).isFile())return r}catch{}return`${n}.ts`}function w(e){return typeof e?.value=="string"?e.value:void 0}function T(e){return e?.name??w(e)}function H(e){return e.sourceCode??e.getSourceCode?.()}function ie(e,t){let n=H(e)?.getScope?.(t);for(;n;){let i=n.references?.find(r=>r.identifier===t);if(i)return i;n=n.upper??void 0}}function ne(e,t,n){let i=ie(e,t);if(i?.resolved)return(i.resolved.defs?.length??0)>0;let r=H(e)?.getScope?.(t);for(;r;){let o=r.set?.get(n);if(o)return(o.defs?.length??0)>0;r=r.upper??void 0}return!1}function Oe(e,t){let n=ie(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function oe(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=oe(e.object),i=T(e.property);if(!(!n||!i))return{root:n.root,segments:[...n.segments,i]}}function $e(e){return T(e.callee?.property)}function se(e,t){return e?.properties?.find(n=>T(n.key)===t)}function C(e,t){return se(e,t)!==void 0}function Pe(e){let t=se(e,"metadata")?.value;return C(t,"source")}function ae(e){return $e(e)==="publish"}var le={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=N(e),n=D(t),i=n?G(n):null,r=n?c.default.dirname(n):null,o=s=>{let a=w(s.source);if(a&&i&&r&&t){let f=c.default.isAbsolute(t)?t:c.default.resolve(t),g=c.default.relative(r,f).split(c.default.sep).join("/"),l=S(g,i.layers);if(!l)return;let u=re(f,a);if(!u)return;let d=c.default.relative(r,u).split(c.default.sep).join("/");if(d.startsWith(".."))return;let p=S(d,i.layers);if(!p)return;P(i.rules,l,p,{fromPath:g,toPath:d,layers:i.layers})&&x(e,s,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:g,fromLayer:l,toLayer:p,target:d,...s.importKind==="type"?{typeOnly:!0}:{},message:`${l} must not import ${p}.`},{fromLayer:l,toLayer:p,specifier:a});return}};return{ImportDeclaration:o,ExportNamedDeclaration:o,ExportAllDeclaration:o}}},ce={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=w(n),r=F({publishCall:ae(t),rawIntentName:i,objectHasIntent:C(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(r.some(o=>o.ruleId==="RAW_EVENT_PUBLISH")){let o=r.find(s=>s.ruleId==="RAW_EVENT_PUBLISH");x(e,t,"rawPublish",{...o,file:N(e)})}}}}},ue={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=t.arguments?.[2],o=F({publishCall:ae(t),rawIntentName:w(n),objectHasIntent:C(n,"intent"),arkPublishCandidate:!0,hasSource:Pe(n)||C(i,"source")}).find(s=>s.ruleId==="PUBLISH_MISSING_SOURCE");o&&x(e,t,"missingSource",{...o,file:N(e)})}}}},fe={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` explicitly overrides."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=N(e),n=e.options?.[0],i=D(t),r=i?G(i):null,o=i?c.default.dirname(i):null,s=null,a="this layer";if(n?.globals)s=new Set(n.globals);else if(r&&o&&t){let l=c.default.isAbsolute(t)?t:c.default.resolve(t),u=c.default.relative(o,l).split(c.default.sep).join("/"),d=r.layers?.find(p=>p.name===S(u,r.layers));d?.forbiddenGlobals?.length?(s=new Set(d.forbiddenGlobals),a=d.name):s=null}if(!s)return{};let f=typeof H(e)?.getScope=="function",g=(l,u)=>{let d=c.default.isAbsolute(t)?t:c.default.resolve(t),p=o?c.default.relative(o,d).split(c.default.sep).join("/"):t;x(e,l,r?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:p,fromLayer:a,target:u,message:`${a} must not use the ambient global "${u}".`},{name:u,layer:a})};return{MemberExpression(l){if(l.parent?.type==="MemberExpression"&&l.parent.object===l)return;let u=oe(l);if(!u||ne(e,u.root,u.segments[0]))return;let d=u.segments[0]==="globalThis",p=d?u.segments.slice(1):u.segments,L;for(let _=p.length;_>=(d?1:2);_-=1){let U=p.slice(0,_).join(".");if(s.has(U)){L=U;break}}L?g(l,L):!f&&s.has(u.segments[0])&&g(l,u.segments[0])},CallExpression(l){if(f)return;let u=l.callee?.type==="Identifier"?l.callee.name:void 0;u&&s.has(u)&&g(l,u)},NewExpression(l){if(f)return;let u=l.callee?.type==="Identifier"?l.callee.name:void 0;u&&s.has(u)&&g(l,u)},Identifier(l){!f||!l.name||!s.has(l.name)||!Oe(e,l)||ne(e,l,l.name)||g(l,l.name)}}}},je={"no-domain-infra-imports":le,"no-raw-event-publish":ce,"require-publish-source":ue,"no-forbidden-globals":fe},E={rules:je};E.configs={recommended:{plugins:{ark:E},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error"}}};var Ve=E;0&&(module.exports={findConfigPath,globToRegExp,isEdgeDenied,layerForRelativePath,loadArkConfig,noDomainInfraImports,noForbiddenGlobals,noRawEventPublish,patternSpecificity,plugin,requirePublishSource,resolveRelativeImport});
|
|
3
|
+
`)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function ne(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function D(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function A(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Oe(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function x(e,t,n,i,r){if(t.$ref){let s=Oe(t.$ref,i);if(!s){r.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}x(e,s,n,i,r);return}if(t.const!==void 0&&!Object.is(e,t.const)){r.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(s=>Object.is(s,e))){r.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!ne(e)){r.push({path:n,message:`must be an object; received ${A(e)}`});return}let s=t.properties??{};for(let l of t.required??[])e[l]===void 0&&r.push({path:D(n,l),message:"is required"});if(t.additionalProperties===!1)for(let l of Object.keys(e))l in s||r.push({path:D(n,l),message:"unknown field"});for(let[l,c]of Object.entries(s))e[l]!==void 0&&x(e[l],c,D(n,l),i,r);return}if(t.type==="array"){if(!Array.isArray(e)){r.push({path:n,message:`must be an array; received ${A(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&r.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let s=e.map(l=>JSON.stringify(l));new Set(s).size!==s.length&&r.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((s,l)=>x(s,t.items,`${n}[${l}]`,i,r));return}if(t.type==="string"){if(typeof e!="string"){r.push({path:n,message:`must be a string; received ${A(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&r.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&r.push({path:n,message:`must be a boolean; received ${A(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){r.push({path:n,message:`must be an integer; received ${A(e)}`});return}t.minimum!==void 0&&e<t.minimum&&r.push({path:n,message:`must be at least ${t.minimum}`})}}function $e(e){return{...e,$schema:e.$schema===void 0?M:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?te.map(t=>({...t})):e.rules}}function Pe(e,t="ark.config.json"){if(!ne(e))throw new k(t,[{path:"$",message:`must be an object; received ${A(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new k(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:$e(e),migratedFrom:n}}function je(e,t="ark.config.json"){let{candidate:n,migratedFrom:i}=Pe(e,t),r=[];if(x(n,ee,"$",ee,r),r.length>0)throw new k(t,r);return{config:n,migratedFrom:i}}function re(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(i){throw new k(t,[{path:"$",message:`invalid JSON: ${i instanceof Error?i.message:String(i)}`}])}return je(n,t)}function m(e){return typeof e=="string"&&e.length>0?e:void 0}function ie(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function Fe(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CAPABILITY_VIOLATION"?`Define a ${m(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function se(e,t="error"){let n=m(e.ruleId)??m(e.code)??"ARK_UNKNOWN",i=e.severity==="warning"?"warning":t,r={...m(e.target)?{target:m(e.target)}:{},...m(e.fromLayer)?{fromLayer:m(e.fromLayer)}:{},...m(e.toLayer)?{toLayer:m(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:i,message:m(e.message)??n,location:{file:m(e.file)??"<unknown>",line:ie(e.line,1),column:ie(e.column,1)},evidence:r,nextAction:m(e.nextAction)??Fe(n,r,e)}}var oe={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function Ve(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function T(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&Ve(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:oe.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:oe.PUBLISH_MISSING_SOURCE}),t}function I(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function w(e,t,n,i,r){let s=se({...i,line:i.line??t.loc?.start?.line,column:i.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...r?{data:r}:{},diagnostic:s}),s}function L(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=u.default.dirname(u.default.resolve(e));for(;;){let n=u.default.join(t,"ark.config.json");if(S.default.existsSync(n))return n;let i=u.default.dirname(t);if(i===t)return null;t=i}}var v=new Map;function _(e){if(v.has(e))return v.get(e)??null;if(!S.default.existsSync(e))return null;let t=re(S.default.readFileSync(e,"utf8"),e).config;return v.set(e,t),t}function ae(e,t){if(!t.startsWith("."))return null;let n=u.default.resolve(u.default.dirname(e),t),i=[n,`${n}.ts`,`${n}.tsx`,`${n}.mts`,`${n}.cts`,`${n}.js`,`${n}.jsx`,u.default.join(n,"index.ts"),u.default.join(n,"index.tsx"),u.default.join(n,"index.js")];for(let r of i)try{if(S.default.existsSync(r)&&S.default.statSync(r).isFile())return r}catch{}return`${n}.ts`}function O(e){return typeof e?.value=="string"?e.value:void 0}function B(e){return e?.name??O(e)}function H(e){return e.sourceCode??e.getSourceCode?.()}function le(e,t){let n=H(e)?.getScope?.(t);for(;n;){let i=n.references?.find(r=>r.identifier===t);if(i)return i;n=n.upper??void 0}}function G(e,t,n){let i=le(e,t);if(i?.resolved)return(i.resolved.defs?.length??0)>0;let r=H(e)?.getScope?.(t);for(;r;){let s=r.set?.get(n);if(s)return(s.defs?.length??0)>0;r=r.upper??void 0}return!1}function De(e,t){let n=le(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function ce(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=ce(e.object),i=B(e.property);if(!(!n||!i))return{root:n.root,segments:[...n.segments,i]}}function Me(e){return B(e.callee?.property)}function ue(e,t){return e?.properties?.find(n=>B(n.key)===t)}function E(e,t){return ue(e,t)!==void 0}function Te(e){let t=ue(e,"metadata")?.value;return E(t,"source")}function de(e){return Me(e)==="publish"}var fe={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=I(e),n=L(t),i=n?_(n):null,r=n?u.default.dirname(n):null,s=l=>{let c=O(l.source);if(c&&i&&r&&t){let p=u.default.isAbsolute(t)?t:u.default.resolve(t),g=u.default.relative(r,p).split(u.default.sep).join("/"),o=h(g,i.layers);if(!o)return;let a=ae(p,c);if(!a)return;let d=u.default.relative(r,a).split(u.default.sep).join("/");if(d.startsWith(".."))return;let f=h(d,i.layers);if(!f)return;F(i.rules,o,f,{fromPath:g,toPath:d,layers:i.layers})&&w(e,l,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:g,fromLayer:o,toLayer:f,target:d,...l.importKind==="type"?{typeOnly:!0}:{},message:`${o} must not import ${f}.`},{fromLayer:o,toLayer:f,specifier:c});return}};return{ImportDeclaration:s,ExportNamedDeclaration:s,ExportAllDeclaration:s}}},pe={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=O(n),r=T({publishCall:de(t),rawIntentName:i,objectHasIntent:E(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(r.some(s=>s.ruleId==="RAW_EVENT_PUBLISH")){let s=r.find(l=>l.ruleId==="RAW_EVENT_PUBLISH");w(e,t,"rawPublish",{...s,file:I(e)})}}}}},ge={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=t.arguments?.[2],s=T({publishCall:de(t),rawIntentName:O(n),objectHasIntent:E(n,"intent"),arkPublishCandidate:!0,hasSource:Te(n)||E(i,"source")}).find(l=>l.ruleId==="PUBLISH_MISSING_SOURCE");s&&w(e,t,"missingSource",{...s,file:I(e)})}}}},me={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` explicitly overrides."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=I(e),n=e.options?.[0],i=L(t),r=i?_(i):null,s=i?u.default.dirname(i):null,l=null,c="this layer";if(n?.globals)l=new Set(n.globals);else if(r&&s&&t){let o=u.default.isAbsolute(t)?t:u.default.resolve(t),a=u.default.relative(s,o).split(u.default.sep).join("/"),d=r.layers?.find(f=>f.name===h(a,r.layers));d?.forbiddenGlobals?.length?(l=new Set(d.forbiddenGlobals),c=d.name):l=null}if(!l)return{};let p=typeof H(e)?.getScope=="function",g=(o,a)=>{let d=u.default.isAbsolute(t)?t:u.default.resolve(t),f=s?u.default.relative(s,d).split(u.default.sep).join("/"):t;w(e,o,r?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:f,fromLayer:c,target:a,message:`${c} must not use the ambient global "${a}".`},{name:a,layer:c})};return{MemberExpression(o){if(o.parent?.type==="MemberExpression"&&o.parent.object===o)return;let a=ce(o);if(!a||G(e,a.root,a.segments[0]))return;let d=a.segments[0]==="globalThis",f=d?a.segments.slice(1):a.segments,y;for(let $=f.length;$>=(d?1:2);$-=1){let K=f.slice(0,$).join(".");if(l.has(K)){y=K;break}}y?g(o,y):!p&&l.has(a.segments[0])&&g(o,a.segments[0])},CallExpression(o){if(p)return;let a=o.callee?.type==="Identifier"?o.callee.name:void 0;a&&l.has(a)&&g(o,a)},NewExpression(o){if(p)return;let a=o.callee?.type==="Identifier"?o.callee.name:void 0;a&&l.has(a)&&g(o,a)},Identifier(o){!p||!o.name||!l.has(o.name)||!De(e,o)||G(e,o,o.name)||g(o,o.name)}}}},ye={meta:{type:"problem",docs:{description:"Disallow importing modules whose effect capability the layer denies (ark.config.json capabilities.deny / pure \u2014 same wall surface as ark-check). Import dimension only: ambient globals stay with no-forbidden-globals and the CLI/hook symbol path."},messages:{deniedCapability:'{{layer}} denies the {{capability}} capability (ark.config.json); "{{specifier}}" imports it. Define a port and bind the implementation in an adapter layer.'},schema:[]},create(e){let t=I(e),n=L(t),i=n?_(n):null,r=n?u.default.dirname(n):null;if(!i||!r||!t)return{};let s=u.default.isAbsolute(t)?t:u.default.resolve(t),l=u.default.relative(r,s).split(u.default.sep).join("/"),c=i.layers?.find(o=>o.name===h(l,i.layers));if(!c)return{};let p=new Set(X(c));if(p.size===0)return{};let g=(o,a,d)=>{if(d||typeof a!="string")return;let f=Z(a);!f||!p.has(f)||w(e,o,"deniedCapability",{ruleId:"CAPABILITY_VIOLATION",file:l,fromLayer:c.name,target:a,capability:f,message:`${c.name} denies the ${f} capability; found import of "${a}".`},{layer:c.name,capability:f,specifier:a})};return{ImportDeclaration(o){let a=o,d=(a.specifiers??[]).filter(y=>y.type==="ImportSpecifier"),f=d.length>0&&d.length===(a.specifiers??[]).length&&d.every(y=>y.importKind==="type");g(o,a.source?.value,a.importKind==="type"||f)},ImportExpression(o){let a=o;a.source?.type==="Literal"&&g(o,a.source.value,!1)},ExportNamedDeclaration(o){let a=o;if(!a.source)return;let d=a.specifiers??[],f=d.length>0&&d.every(y=>y.exportKind==="type");g(o,a.source.value,a.exportKind==="type"||f)},ExportAllDeclaration(o){let a=o;g(o,a.source?.value,a.exportKind==="type")},CallExpression(o){let a=o;a.callee?.type==="Identifier"&&a.callee.name==="require"&&a.arguments?.[0]?.type==="Literal"&&!G(e,o,"require")&&g(o,a.arguments[0].value,!1)}}}},ve={"no-domain-infra-imports":fe,"no-raw-event-publish":pe,"require-publish-source":ge,"no-forbidden-globals":me,"no-denied-capabilities":ye},N={rules:ve};N.configs={recommended:{plugins:{ark:N},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error","ark/no-denied-capabilities":"error"}}};var Ge=N;0&&(module.exports={findConfigPath,globToRegExp,isEdgeDenied,layerForRelativePath,loadArkConfig,noDeniedCapabilities,noDomainInfraImports,noForbiddenGlobals,noRawEventPublish,patternSpecificity,plugin,requirePublishSource,resolveRelativeImport});
|
package/dist/eslint/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as ArkConfig } from '../configTypes-
|
|
1
|
+
import { A as ArkConfig } from '../configTypes-DAPvBqK6.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Pure layer-glob matching for ark.config.json.
|
|
@@ -138,8 +138,9 @@ declare const noDomainInfraImports: ArkRule;
|
|
|
138
138
|
declare const noRawEventPublish: ArkRule;
|
|
139
139
|
declare const requirePublishSource: ArkRule;
|
|
140
140
|
declare const noForbiddenGlobals: ArkRule;
|
|
141
|
+
declare const noDeniedCapabilities: ArkRule;
|
|
141
142
|
declare const plugin: ArkEslintPlugin;
|
|
142
143
|
|
|
143
144
|
// @ts-ignore
|
|
144
145
|
export = plugin;
|
|
145
|
-
export { findConfigPath, globToRegExp, isEdgeDenied, layerForRelativePath, loadArkConfig, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, patternSpecificity, plugin, requirePublishSource, resolveRelativeImport };
|
|
146
|
+
export { findConfigPath, globToRegExp, isEdgeDenied, layerForRelativePath, loadArkConfig, noDeniedCapabilities, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, patternSpecificity, plugin, requirePublishSource, resolveRelativeImport };
|
package/dist/eslint/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as ArkConfig } from '../configTypes-
|
|
1
|
+
import { A as ArkConfig } from '../configTypes-DAPvBqK6.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Pure layer-glob matching for ark.config.json.
|
|
@@ -138,6 +138,7 @@ declare const noDomainInfraImports: ArkRule;
|
|
|
138
138
|
declare const noRawEventPublish: ArkRule;
|
|
139
139
|
declare const requirePublishSource: ArkRule;
|
|
140
140
|
declare const noForbiddenGlobals: ArkRule;
|
|
141
|
+
declare const noDeniedCapabilities: ArkRule;
|
|
141
142
|
declare const plugin: ArkEslintPlugin;
|
|
142
143
|
|
|
143
|
-
export { plugin as default, findConfigPath, globToRegExp, isEdgeDenied, layerForRelativePath, loadArkConfig, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, patternSpecificity, plugin, requirePublishSource, resolveRelativeImport };
|
|
144
|
+
export { plugin as default, findConfigPath, globToRegExp, isEdgeDenied, layerForRelativePath, loadArkConfig, noDeniedCapabilities, noDomainInfraImports, noForbiddenGlobals, noRawEventPublish, patternSpecificity, plugin, requirePublishSource, resolveRelativeImport };
|
package/dist/eslint/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
1
|
+
import S from"fs";import u from"path";var B=new Map;function H(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function L(e){let t="";for(let n=0;n<e.length;n+=1){let i=e[n];if(i==="\\"&&n+1<e.length){let r=e[n+1];if("*?{}[],".includes(r)||r==="\\"){t+="\\"+r,n+=1;continue}t+="/";continue}t+=i}return t}function le(e){let t=0;for(let n=0;n<e.length;n+=1){let i=e[n];if(i==="\\"){n+=1;continue}if(i==="{")t+=1;else if(i==="}"&&(t-=1,t<0))return!1}return t===0}function N(e){let t=B.get(e);if(t)return t;let n=L(e),i=le(n),r="",s=0;for(let c=0;c<n.length;c+=1){let p=n[c];p==="\\"&&c+1<n.length?(r+=H(n[c+1]),c+=1):p==="*"?n[c+1]==="*"?n[c+2]==="/"?(r+="(?:.*/)?",c+=2):(r+=".*",c+=1):r+="[^/]*":p==="?"?r+="[^/]":p==="{"&&i?(r+="(?:",s+=1):p==="}"&&i&&s>0?(r+=")",s-=1):p===","&&i&&s>0?r+="|":r+=H(p)}let l=new RegExp(`^${r}$`);return B.set(e,l),l}function U(e){let t=L(String(e)),i=t.split("*")[0].split("/").filter(Boolean).length,r=t.replace(/\*/g,"").length;return i*1e4+r}function k(e,t){let n=String(e).split(/[/\\]/).join("/"),i,r=-1;for(let s of t??[])if(!(s.exclude??[]).some(l=>N(l).test(n))){for(let l of s.patterns??[])if(N(l).test(n)){let c=U(l);c>r&&(r=c,i=s.name)}}return i}function K(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),i=new Set(t.map(r=>String(r).toLowerCase()));for(let r=0;r<n.length-1;r+=1)if(i.has(n[r].toLowerCase()))return`${n[r]}/${n[r+1]}`}function ce(e){let t=new Set;for(let n of e??[]){let r=L(String(n)).split("/").filter(Boolean);for(let s=0;s<r.length;s+=1){let l=r[s];if((l==="**"||l==="*")&&s>0){let c=r[s-1];c&&!c.includes("*")&&!c.includes("{")&&!c.includes("}")&&t.add(c)}}}return[...t]}function ue(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(r=>typeof r=="string"&&r.length>0);let i=(n??[]).find(r=>r.name===t);return ce(i?.patterns)}function de(e,t,n,i){for(let r of e??[])if(!(r.from!==t||r.to!==n)&&r.allowed===!1){if(r.peerIsolation){let s=i?.fromPath,l=i?.toPath;if(!s||!l)continue;let c=ue(r,t,i?.layers);if(c.length===0)continue;let p=K(s,c),g=K(l,c);if(!p||!g)continue;if(p!==g)return r;continue}if(t!==n)return r}}function q(e,t,n,i){return de(e,t,n,i)!==void 0}var W=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),fe=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),$e=Object.freeze(Object.keys(fe).sort()),_=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function Y(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=_[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let i=e.slice(0,n),r=_[i];if(r)return r;let s=e.indexOf("/",n+1);return s<0?null:_[e.slice(0,s)]??null}function z(e){if(e?.pure===!0)return[...W].sort();let n=(e?.capabilities?.deny??[]).filter(i=>W.includes(i));return[...new Set(n)].sort()}var $="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",J=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],pe=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function ge(){let e=[];for(let t of J)for(let n of J)t===n||pe.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var X=ge();var b={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},Z={$schema:"https://json-schema.org/draft/2020-12/schema",$id:$,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:$,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.0",default:"1.0"},name:{type:"string",minLength:1},include:{...b,minItems:1,default:["src"]},exclude:{...b,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:X,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...b,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...b,minItems:1},exclude:b,intentPrefixes:b,description:{type:"string",minLength:1},forbiddenGlobals:b,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...b,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},A=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}):
|
|
2
2
|
${n.map(i=>`- ${i.path}: ${i.message}`).join(`
|
|
3
|
-
`)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function W(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function _(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function h(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function ue(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function k(e,t,n,i,r){if(t.$ref){let o=ue(t.$ref,i);if(!o){r.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}k(e,o,n,i,r);return}if(t.const!==void 0&&!Object.is(e,t.const)){r.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(o=>Object.is(o,e))){r.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!W(e)){r.push({path:n,message:`must be an object; received ${h(e)}`});return}let o=t.properties??{};for(let s of t.required??[])e[s]===void 0&&r.push({path:_(n,s),message:"is required"});if(t.additionalProperties===!1)for(let s of Object.keys(e))s in o||r.push({path:_(n,s),message:"unknown field"});for(let[s,a]of Object.entries(o))e[s]!==void 0&&k(e[s],a,_(n,s),i,r);return}if(t.type==="array"){if(!Array.isArray(e)){r.push({path:n,message:`must be an array; received ${h(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&r.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let o=e.map(s=>JSON.stringify(s));new Set(o).size!==o.length&&r.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((o,s)=>k(o,t.items,`${n}[${s}]`,i,r));return}if(t.type==="string"){if(typeof e!="string"){r.push({path:n,message:`must be a string; received ${h(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&r.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&r.push({path:n,message:`must be a boolean; received ${h(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){r.push({path:n,message:`must be an integer; received ${h(e)}`});return}t.minimum!==void 0&&e<t.minimum&&r.push({path:n,message:`must be at least ${t.minimum}`})}}function fe(e){return{...e,$schema:e.$schema===void 0?O:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?K.map(t=>({...t})):e.rules}}function de(e,t="ark.config.json"){if(!W(e))throw new A(t,[{path:"$",message:`must be an object; received ${h(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new A(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:fe(e),migratedFrom:n}}function ge(e,t="ark.config.json"){let{candidate:n,migratedFrom:i}=de(e,t),r=[];if(k(n,B,"$",B,r),r.length>0)throw new A(t,r);return{config:n,migratedFrom:i}}function q(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(i){throw new A(t,[{path:"$",message:`invalid JSON: ${i instanceof Error?i.message:String(i)}`}])}return ge(n,t)}function m(e){return typeof e=="string"&&e.length>0?e:void 0}function J(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function pe(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function Y(e,t="error"){let n=m(e.ruleId)??m(e.code)??"ARK_UNKNOWN",i=e.severity==="warning"?"warning":t,r={...m(e.target)?{target:m(e.target)}:{},...m(e.fromLayer)?{fromLayer:m(e.fromLayer)}:{},...m(e.toLayer)?{toLayer:m(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:i,message:m(e.message)??n,location:{file:m(e.file)??"<unknown>",line:J(e.line,1),column:J(e.column,1)},evidence:r,nextAction:m(e.nextAction)??pe(n,r,e)}}var z={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function me(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function $(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&me(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:z.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:z.PUBLISH_MISSING_SOURCE}),t}function I(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function C(e,t,n,i,r){let o=Y({...i,line:i.line??t.loc?.start?.line,column:i.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...r?{data:r}:{},diagnostic:o}),o}function Q(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=u.dirname(u.resolve(e));for(;;){let n=u.join(t,"ark.config.json");if(b.existsSync(n))return n;let i=u.dirname(t);if(i===t)return null;t=i}}var P=new Map;function X(e){if(P.has(e))return P.get(e)??null;if(!b.existsSync(e))return null;let t=q(b.readFileSync(e,"utf8"),e).config;return P.set(e,t),t}function ye(e,t){if(!t.startsWith("."))return null;let n=u.resolve(u.dirname(e),t),i=[n,`${n}.ts`,`${n}.tsx`,`${n}.mts`,`${n}.cts`,`${n}.js`,`${n}.jsx`,u.join(n,"index.ts"),u.join(n,"index.tsx"),u.join(n,"index.js")];for(let r of i)try{if(b.existsSync(r)&&b.statSync(r).isFile())return r}catch{}return`${n}.ts`}function E(e){return typeof e?.value=="string"?e.value:void 0}function V(e){return e?.name??E(e)}function F(e){return e.sourceCode??e.getSourceCode?.()}function ee(e,t){let n=F(e)?.getScope?.(t);for(;n;){let i=n.references?.find(r=>r.identifier===t);if(i)return i;n=n.upper??void 0}}function Z(e,t,n){let i=ee(e,t);if(i?.resolved)return(i.resolved.defs?.length??0)>0;let r=F(e)?.getScope?.(t);for(;r;){let o=r.set?.get(n);if(o)return(o.defs?.length??0)>0;r=r.upper??void 0}return!1}function he(e,t){let n=ee(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function te(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=te(e.object),i=V(e.property);if(!(!n||!i))return{root:n.root,segments:[...n.segments,i]}}function Ae(e){return V(e.callee?.property)}function ne(e,t){return e?.properties?.find(n=>V(n.key)===t)}function R(e,t){return ne(e,t)!==void 0}function be(e){let t=ne(e,"metadata")?.value;return R(t,"source")}function re(e){return Ae(e)==="publish"}var Se={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=I(e),n=Q(t),i=n?X(n):null,r=n?u.dirname(n):null,o=s=>{let a=E(s.source);if(a&&i&&r&&t){let f=u.isAbsolute(t)?t:u.resolve(t),g=u.relative(r,f).split(u.sep).join("/"),l=S(g,i.layers);if(!l)return;let c=ye(f,a);if(!c)return;let d=u.relative(r,c).split(u.sep).join("/");if(d.startsWith(".."))return;let p=S(d,i.layers);if(!p)return;U(i.rules,l,p,{fromPath:g,toPath:d,layers:i.layers})&&C(e,s,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:g,fromLayer:l,toLayer:p,target:d,...s.importKind==="type"?{typeOnly:!0}:{},message:`${l} must not import ${p}.`},{fromLayer:l,toLayer:p,specifier:a});return}};return{ImportDeclaration:o,ExportNamedDeclaration:o,ExportAllDeclaration:o}}},ke={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=E(n),r=$({publishCall:re(t),rawIntentName:i,objectHasIntent:R(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(r.some(o=>o.ruleId==="RAW_EVENT_PUBLISH")){let o=r.find(s=>s.ruleId==="RAW_EVENT_PUBLISH");C(e,t,"rawPublish",{...o,file:I(e)})}}}}},Re={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=t.arguments?.[2],o=$({publishCall:re(t),rawIntentName:E(n),objectHasIntent:R(n,"intent"),arkPublishCandidate:!0,hasSource:be(n)||R(i,"source")}).find(s=>s.ruleId==="PUBLISH_MISSING_SOURCE");o&&C(e,t,"missingSource",{...o,file:I(e)})}}}},Ie={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` explicitly overrides."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=I(e),n=e.options?.[0],i=Q(t),r=i?X(i):null,o=i?u.dirname(i):null,s=null,a="this layer";if(n?.globals)s=new Set(n.globals);else if(r&&o&&t){let l=u.isAbsolute(t)?t:u.resolve(t),c=u.relative(o,l).split(u.sep).join("/"),d=r.layers?.find(p=>p.name===S(c,r.layers));d?.forbiddenGlobals?.length?(s=new Set(d.forbiddenGlobals),a=d.name):s=null}if(!s)return{};let f=typeof F(e)?.getScope=="function",g=(l,c)=>{let d=u.isAbsolute(t)?t:u.resolve(t),p=o?u.relative(o,d).split(u.sep).join("/"):t;C(e,l,r?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:p,fromLayer:a,target:c,message:`${a} must not use the ambient global "${c}".`},{name:c,layer:a})};return{MemberExpression(l){if(l.parent?.type==="MemberExpression"&&l.parent.object===l)return;let c=te(l);if(!c||Z(e,c.root,c.segments[0]))return;let d=c.segments[0]==="globalThis",p=d?c.segments.slice(1):c.segments,N;for(let x=p.length;x>=(d?1:2);x-=1){let M=p.slice(0,x).join(".");if(s.has(M)){N=M;break}}N?g(l,N):!f&&s.has(c.segments[0])&&g(l,c.segments[0])},CallExpression(l){if(f)return;let c=l.callee?.type==="Identifier"?l.callee.name:void 0;c&&s.has(c)&&g(l,c)},NewExpression(l){if(f)return;let c=l.callee?.type==="Identifier"?l.callee.name:void 0;c&&s.has(c)&&g(l,c)},Identifier(l){!f||!l.name||!s.has(l.name)||!he(e,l)||Z(e,l,l.name)||g(l,l.name)}}}},Ce={"no-domain-infra-imports":Se,"no-raw-event-publish":ke,"require-publish-source":Re,"no-forbidden-globals":Ie},j={rules:Ce};j.configs={recommended:{plugins:{ark:j},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error"}}};var Ve=j;export{Ve as default,Q as findConfigPath,w as globToRegExp,U as isEdgeDenied,S as layerForRelativePath,X as loadArkConfig,Se as noDomainInfraImports,Ie as noForbiddenGlobals,ke as noRawEventPublish,H as patternSpecificity,j as plugin,Re as requirePublishSource,ye as resolveRelativeImport};
|
|
3
|
+
`)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function Q(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function O(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function h(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function me(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function C(e,t,n,i,r){if(t.$ref){let s=me(t.$ref,i);if(!s){r.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}C(e,s,n,i,r);return}if(t.const!==void 0&&!Object.is(e,t.const)){r.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(s=>Object.is(s,e))){r.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!Q(e)){r.push({path:n,message:`must be an object; received ${h(e)}`});return}let s=t.properties??{};for(let l of t.required??[])e[l]===void 0&&r.push({path:O(n,l),message:"is required"});if(t.additionalProperties===!1)for(let l of Object.keys(e))l in s||r.push({path:O(n,l),message:"unknown field"});for(let[l,c]of Object.entries(s))e[l]!==void 0&&C(e[l],c,O(n,l),i,r);return}if(t.type==="array"){if(!Array.isArray(e)){r.push({path:n,message:`must be an array; received ${h(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&r.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let s=e.map(l=>JSON.stringify(l));new Set(s).size!==s.length&&r.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((s,l)=>C(s,t.items,`${n}[${l}]`,i,r));return}if(t.type==="string"){if(typeof e!="string"){r.push({path:n,message:`must be a string; received ${h(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&r.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&r.push({path:n,message:`must be a boolean; received ${h(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){r.push({path:n,message:`must be an integer; received ${h(e)}`});return}t.minimum!==void 0&&e<t.minimum&&r.push({path:n,message:`must be at least ${t.minimum}`})}}function ye(e){return{...e,$schema:e.$schema===void 0?$:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?X.map(t=>({...t})):e.rules}}function be(e,t="ark.config.json"){if(!Q(e))throw new A(t,[{path:"$",message:`must be an object; received ${h(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new A(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:ye(e),migratedFrom:n}}function he(e,t="ark.config.json"){let{candidate:n,migratedFrom:i}=be(e,t),r=[];if(C(n,Z,"$",Z,r),r.length>0)throw new A(t,r);return{config:n,migratedFrom:i}}function ee(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(i){throw new A(t,[{path:"$",message:`invalid JSON: ${i instanceof Error?i.message:String(i)}`}])}return he(n,t)}function m(e){return typeof e=="string"&&e.length>0?e:void 0}function te(e,t){return Number.isInteger(e)&&Number(e)>0?Number(e):t}function Ae(e,t,n){return e==="LAYER_IMPORT_VIOLATION"?t.typeOnly||n.targetTypeOnlyExports===!0||n.namedBindingsTypeOnly===!0?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":n.peerIsolation===!0?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${t.fromLayer??"the source layer"}, inject the ${t.toLayer??"outer-layer"} implementation, then preflight again.`:e==="FORBIDDEN_GLOBAL"?`Inject ${t.target??"the capability"} through a port, then preflight again.`:e==="CAPABILITY_VIOLATION"?`Define a ${m(n.capability)??"capability"} port in ${t.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`:e==="CIRCULAR_DEPENDENCY"?"Extract the shared dependency into a third module, then preflight again.":e==="RAW_EVENT_PUBLISH"?"Publish through a registered intent creator, then run Ark again.":e==="PUBLISH_MISSING_SOURCE"?"Add metadata.source to the publish call, then run Ark again.":`Resolve ${e} without weakening ark.config.json, then run Ark again.`}function ne(e,t="error"){let n=m(e.ruleId)??m(e.code)??"ARK_UNKNOWN",i=e.severity==="warning"?"warning":t,r={...m(e.target)?{target:m(e.target)}:{},...m(e.fromLayer)?{fromLayer:m(e.fromLayer)}:{},...m(e.toLayer)?{toLayer:m(e.toLayer)}:{},...typeof e.typeOnly=="boolean"?{typeOnly:e.typeOnly}:{}};return{ruleId:n,severity:i,message:m(e.message)??n,location:{file:m(e.file)??"<unknown>",line:te(e.line,1),column:te(e.column,1)},evidence:r,nextAction:m(e.nextAction)??Ae(n,r,e)}}var re={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function ke(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function P(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&ke(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:re.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:re.PUBLISH_MISSING_SOURCE}),t}function I(e){if(typeof e.physicalFilename=="string"&&e.physicalFilename.length>0)return e.physicalFilename;if(typeof e.filename=="string"&&e.filename.length>0)return e.filename;if(typeof e.getFilename=="function")try{let t=e.getFilename();if(typeof t=="string"&&t.length>0)return t}catch{}return""}function w(e,t,n,i,r){let s=ne({...i,line:i.line??t.loc?.start?.line,column:i.column??(typeof t.loc?.start?.column=="number"?t.loc.start.column+1:void 0)});return e.report({node:t,messageId:n,...r?{data:r}:{},diagnostic:s}),s}function D(e){if(!e||e==="<input>"||e.startsWith("stdin"))return null;let t=u.dirname(u.resolve(e));for(;;){let n=u.join(t,"ark.config.json");if(S.existsSync(n))return n;let i=u.dirname(t);if(i===t)return null;t=i}}var j=new Map;function M(e){if(j.has(e))return j.get(e)??null;if(!S.existsSync(e))return null;let t=ee(S.readFileSync(e,"utf8"),e).config;return j.set(e,t),t}function Se(e,t){if(!t.startsWith("."))return null;let n=u.resolve(u.dirname(e),t),i=[n,`${n}.ts`,`${n}.tsx`,`${n}.mts`,`${n}.cts`,`${n}.js`,`${n}.jsx`,u.join(n,"index.ts"),u.join(n,"index.tsx"),u.join(n,"index.js")];for(let r of i)try{if(S.existsSync(r)&&S.statSync(r).isFile())return r}catch{}return`${n}.ts`}function x(e){return typeof e?.value=="string"?e.value:void 0}function T(e){return e?.name??x(e)}function v(e){return e.sourceCode??e.getSourceCode?.()}function ie(e,t){let n=v(e)?.getScope?.(t);for(;n;){let i=n.references?.find(r=>r.identifier===t);if(i)return i;n=n.upper??void 0}}function F(e,t,n){let i=ie(e,t);if(i?.resolved)return(i.resolved.defs?.length??0)>0;let r=v(e)?.getScope?.(t);for(;r;){let s=r.set?.get(n);if(s)return(s.defs?.length??0)>0;r=r.upper??void 0}return!1}function Ie(e,t){let n=ie(e,t);return n?n.isValueReference!==!1:t.parent?.type==="VariableDeclarator"&&t.parent.init===t}function se(e){if(e?.type==="Identifier"&&e.name)return{root:e,segments:[e.name]};if(!e||!(e.type==="MemberExpression"||!!(e.object&&e.property))||e.computed===!0)return;let n=se(e.object),i=T(e.property);if(!(!n||!i))return{root:n.root,segments:[...n.segments,i]}}function we(e){return T(e.callee?.property)}function oe(e,t){return e?.properties?.find(n=>T(n.key)===t)}function R(e,t){return oe(e,t)!==void 0}function Ce(e){let t=oe(e,"metadata")?.value;return R(t,"source")}function ae(e){return we(e)==="publish"}var Re={meta:{type:"problem",docs:{description:"Disallow imports that violate ark.config.json layer rules (same contract as arkgate-check)."},messages:{forbiddenImport:"Architecture: {{fromLayer}} must not import {{toLayer}} (ark.config.json). Specifier: {{specifier}}",forbiddenImportHeuristic:"Domain code must not import infrastructure, adapters, repositories, or database modules."},schema:[]},create(e){let t=I(e),n=D(t),i=n?M(n):null,r=n?u.dirname(n):null,s=l=>{let c=x(l.source);if(c&&i&&r&&t){let p=u.isAbsolute(t)?t:u.resolve(t),g=u.relative(r,p).split(u.sep).join("/"),o=k(g,i.layers);if(!o)return;let a=Se(p,c);if(!a)return;let d=u.relative(r,a).split(u.sep).join("/");if(d.startsWith(".."))return;let f=k(d,i.layers);if(!f)return;q(i.rules,o,f,{fromPath:g,toPath:d,layers:i.layers})&&w(e,l,"forbiddenImport",{ruleId:"LAYER_IMPORT_VIOLATION",file:g,fromLayer:o,toLayer:f,target:d,...l.importKind==="type"?{typeOnly:!0}:{},message:`${o} must not import ${f}.`},{fromLayer:o,toLayer:f,specifier:c});return}};return{ImportDeclaration:s,ExportNamedDeclaration:s,ExportAllDeclaration:s}}},xe={meta:{type:"problem",docs:{description:"Require event bus publish calls to use registered intent creators instead of raw event objects or intent strings."},messages:{rawPublish:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=x(n),r=P({publishCall:ae(t),rawIntentName:i,objectHasIntent:R(n,"intent"),arkPublishCandidate:!1,hasSource:!0});if(r.some(s=>s.ruleId==="RAW_EVENT_PUBLISH")){let s=r.find(l=>l.ruleId==="RAW_EVENT_PUBLISH");w(e,t,"rawPublish",{...s,file:I(e)})}}}}},Ee={meta:{type:"problem",docs:{description:"Require event bus publish calls to include source metadata."},messages:{missingSource:"Strict Ark publish calls must include metadata.source."},schema:[]},create(e){return{CallExpression(t){let n=t.arguments?.[0],i=t.arguments?.[2],s=P({publishCall:ae(t),rawIntentName:x(n),objectHasIntent:R(n,"intent"),arkPublishCandidate:!0,hasSource:Ce(n)||R(i,"source")}).find(l=>l.ruleId==="PUBLISH_MISSING_SOURCE");s&&w(e,t,"missingSource",{...s,file:I(e)})}}}},Ne={meta:{type:"problem",docs:{description:"Disallow ambient globals from the layer\u2019s forbiddenGlobals in ark.config.json (same purity surface as ark-check). Option `globals` explicitly overrides."},messages:{forbiddenGlobal:'Ambient global "{{name}}" is forbidden in {{layer}} (ark.config.json); inject the capability through a port instead.',forbiddenGlobalDefault:'Ambient global "{{name}}" is forbidden here; inject the capability through a port instead.'},schema:[{type:"object",properties:{globals:{type:"array",items:{type:"string"}}},additionalProperties:!1}]},create(e){let t=I(e),n=e.options?.[0],i=D(t),r=i?M(i):null,s=i?u.dirname(i):null,l=null,c="this layer";if(n?.globals)l=new Set(n.globals);else if(r&&s&&t){let o=u.isAbsolute(t)?t:u.resolve(t),a=u.relative(s,o).split(u.sep).join("/"),d=r.layers?.find(f=>f.name===k(a,r.layers));d?.forbiddenGlobals?.length?(l=new Set(d.forbiddenGlobals),c=d.name):l=null}if(!l)return{};let p=typeof v(e)?.getScope=="function",g=(o,a)=>{let d=u.isAbsolute(t)?t:u.resolve(t),f=s?u.relative(s,d).split(u.sep).join("/"):t;w(e,o,r?"forbiddenGlobal":"forbiddenGlobalDefault",{ruleId:"FORBIDDEN_GLOBAL",file:f,fromLayer:c,target:a,message:`${c} must not use the ambient global "${a}".`},{name:a,layer:c})};return{MemberExpression(o){if(o.parent?.type==="MemberExpression"&&o.parent.object===o)return;let a=se(o);if(!a||F(e,a.root,a.segments[0]))return;let d=a.segments[0]==="globalThis",f=d?a.segments.slice(1):a.segments,y;for(let E=f.length;E>=(d?1:2);E-=1){let G=f.slice(0,E).join(".");if(l.has(G)){y=G;break}}y?g(o,y):!p&&l.has(a.segments[0])&&g(o,a.segments[0])},CallExpression(o){if(p)return;let a=o.callee?.type==="Identifier"?o.callee.name:void 0;a&&l.has(a)&&g(o,a)},NewExpression(o){if(p)return;let a=o.callee?.type==="Identifier"?o.callee.name:void 0;a&&l.has(a)&&g(o,a)},Identifier(o){!p||!o.name||!l.has(o.name)||!Ie(e,o)||F(e,o,o.name)||g(o,o.name)}}}},Le={meta:{type:"problem",docs:{description:"Disallow importing modules whose effect capability the layer denies (ark.config.json capabilities.deny / pure \u2014 same wall surface as ark-check). Import dimension only: ambient globals stay with no-forbidden-globals and the CLI/hook symbol path."},messages:{deniedCapability:'{{layer}} denies the {{capability}} capability (ark.config.json); "{{specifier}}" imports it. Define a port and bind the implementation in an adapter layer.'},schema:[]},create(e){let t=I(e),n=D(t),i=n?M(n):null,r=n?u.dirname(n):null;if(!i||!r||!t)return{};let s=u.isAbsolute(t)?t:u.resolve(t),l=u.relative(r,s).split(u.sep).join("/"),c=i.layers?.find(o=>o.name===k(l,i.layers));if(!c)return{};let p=new Set(z(c));if(p.size===0)return{};let g=(o,a,d)=>{if(d||typeof a!="string")return;let f=Y(a);!f||!p.has(f)||w(e,o,"deniedCapability",{ruleId:"CAPABILITY_VIOLATION",file:l,fromLayer:c.name,target:a,capability:f,message:`${c.name} denies the ${f} capability; found import of "${a}".`},{layer:c.name,capability:f,specifier:a})};return{ImportDeclaration(o){let a=o,d=(a.specifiers??[]).filter(y=>y.type==="ImportSpecifier"),f=d.length>0&&d.length===(a.specifiers??[]).length&&d.every(y=>y.importKind==="type");g(o,a.source?.value,a.importKind==="type"||f)},ImportExpression(o){let a=o;a.source?.type==="Literal"&&g(o,a.source.value,!1)},ExportNamedDeclaration(o){let a=o;if(!a.source)return;let d=a.specifiers??[],f=d.length>0&&d.every(y=>y.exportKind==="type");g(o,a.source.value,a.exportKind==="type"||f)},ExportAllDeclaration(o){let a=o;g(o,a.source?.value,a.exportKind==="type")},CallExpression(o){let a=o;a.callee?.type==="Identifier"&&a.callee.name==="require"&&a.arguments?.[0]?.type==="Literal"&&!F(e,o,"require")&&g(o,a.arguments[0].value,!1)}}}},_e={"no-domain-infra-imports":Re,"no-raw-event-publish":xe,"require-publish-source":Ee,"no-forbidden-globals":Ne,"no-denied-capabilities":Le},V={rules:_e};V.configs={recommended:{plugins:{ark:V},rules:{"ark/no-domain-infra-imports":"error","ark/no-raw-event-publish":"error","ark/require-publish-source":"error","ark/no-forbidden-globals":"error","ark/no-denied-capabilities":"error"}}};var Ke=V;export{Ke as default,D as findConfigPath,N as globToRegExp,q as isEdgeDenied,k as layerForRelativePath,M as loadArkConfig,Le as noDeniedCapabilities,Re as noDomainInfraImports,Ne as noForbiddenGlobals,xe as noRawEventPublish,U as patternSpecificity,V as plugin,Ee as requirePublishSource,Se as resolveRelativeImport};
|