arkgate 4.7.5 → 4.8.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 +39 -3
- package/README.md +24 -12
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/ark-order-error.mjs +18 -0
- package/bin/lib/ark-order-facts.mjs +53 -0
- package/bin/lib/ark-order-invariants.mjs +160 -0
- package/bin/lib/ark-order-sensors.mjs +118 -0
- package/bin/lib/ark-order-types.mjs +11 -0
- package/bin/lib/ark-run-sensors.mjs +13 -5
- package/bin/lib/config-contract.mjs +16 -72
- package/bin/lib/config-extras.mjs +151 -0
- package/bin/lib/diagnostic-catalog.mjs +7 -2
- package/bin/lib/extra-merge-teeth.mjs +8 -2
- package/bin/lib/install-migrate.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +1 -1
- package/bin/lib/mcp-adoption.mjs +9 -3
- package/bin/lib/remediation.mjs +48 -9
- package/bin/lib/resolved-candidate-facts.mjs +43 -0
- package/bin/lib/skill-install.mjs +17 -2
- package/bin/lib/start-preview.mjs +1 -0
- package/bin/lib/status-manifest.mjs +1 -1
- package/bin/lib/write-path-capabilities.mjs +2 -2
- package/dist/{configTypes-CgJimx9o.d.ts → configTypes-BdCe_gvv.d.ts} +22 -6
- package/dist/diagnosticCatalog-RiKPUFRG.d.ts +2307 -0
- package/dist/eslint/index.cjs +7 -6
- package/dist/eslint/index.d.ts +33 -2
- package/dist/eslint/index.js +7 -6
- package/dist/index.cjs +35 -35
- package/dist/index.d.ts +271 -2554
- package/dist/index.js +35 -35
- package/dist/nestjs/index.cjs +18 -0
- package/dist/nestjs/index.d.ts +24 -0
- package/dist/nestjs/index.js +18 -0
- package/dist/order/index.cjs +1 -0
- package/dist/order/index.d.ts +79 -0
- package/dist/order/index.js +1 -0
- package/dist/runtime/index.cjs +25 -0
- package/dist/runtime/index.d.ts +497 -0
- package/dist/runtime/index.js +25 -0
- package/dist/types-C9KApBzX.d.ts +1237 -0
- package/dist/types-DCSlrRnV.d.ts +181 -0
- package/docs/README.md +4 -5
- package/docs/agent-guide.md +1 -1
- package/docs/ai-gates.md +9 -2
- package/docs/configuration.md +13 -6
- package/docs/develop.md +12 -3
- package/docs/diagnostics.md +57 -3
- package/docs/package-surface.md +21 -17
- package/docs/product-voice.md +2 -1
- package/docs/use.md +8 -0
- package/package.json +21 -2
- package/schemas/ark.config.schema.json +54 -2
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +2 -2
- package/templates/agent-skills/ark-contract/SKILL.md +1 -1
- package/templates/agent-skills/ark-place/SKILL.md +15 -6
- package/templates/agent-skills/ark-runtime/SKILL.md +10 -15
- package/templates/skills/ark-adopt.md +2 -2
- package/templates/skills/ark-contract.md +1 -1
- package/templates/skills/ark-place.md +15 -6
- package/templates/skills/ark-runtime.md +10 -15
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/arkOrderError.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/ark-order-error.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class ArkOrderError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code, message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'ArkOrderError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/arkOrderFacts.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/ark-order-facts.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const ARKORDER_PLANE_FACTORY = 'createOrderPlane';
|
|
12
|
+
export const ARKORDER_FORBIDDEN_METHODS = ['update', 'patch', 'set', 'mutate'];
|
|
13
|
+
export function isArkOrderModuleSpecifier(specifier) {
|
|
14
|
+
return specifier === 'arkgate/order' || specifier.startsWith('arkgate/order/');
|
|
15
|
+
}
|
|
16
|
+
function lineAt(content, index) {
|
|
17
|
+
let line = 1;
|
|
18
|
+
for (let i = 0; i < index && i < content.length; i += 1) {
|
|
19
|
+
if (content[i] === '\n')
|
|
20
|
+
line += 1;
|
|
21
|
+
}
|
|
22
|
+
return line;
|
|
23
|
+
}
|
|
24
|
+
function stripCommentsPreservingLines(content) {
|
|
25
|
+
return content
|
|
26
|
+
.replace(/\/\*[\s\S]*?\*\//g, (block) => block.replace(/[^\n]/g, ' '))
|
|
27
|
+
.replace(/(^|[^:])\/\/.*$/gm, (line) => line.replace(/\/\/.*$/, (c) => ' '.repeat(c.length)));
|
|
28
|
+
}
|
|
29
|
+
export function extractArkOrderPlaneCallsFromSource(file, content) {
|
|
30
|
+
const source = stripCommentsPreservingLines(content);
|
|
31
|
+
const facts = [];
|
|
32
|
+
const re = /\bcreateOrderPlane\s*(?:<[^>]*>)?\s*\(/g;
|
|
33
|
+
let match;
|
|
34
|
+
while ((match = re.exec(source)) !== null) {
|
|
35
|
+
facts.push({ file, line: lineAt(content, match.index), callee: ARKORDER_PLANE_FACTORY });
|
|
36
|
+
}
|
|
37
|
+
return facts;
|
|
38
|
+
}
|
|
39
|
+
export function extractArkOrderGenericUpdatesFromSource(file, content) {
|
|
40
|
+
const source = stripCommentsPreservingLines(content);
|
|
41
|
+
const facts = [];
|
|
42
|
+
const re = /\.((?:update|patch|set|mutate))\s*\(/g;
|
|
43
|
+
let match;
|
|
44
|
+
while ((match = re.exec(source)) !== null) {
|
|
45
|
+
const method = match[1];
|
|
46
|
+
const before = source.slice(Math.max(0, match.index - 80), match.index);
|
|
47
|
+
if (!/\b(?:plane|orderPlane|order)\s*$/.test(before) && !/\bcreateOrderPlane\b/.test(source)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
facts.push({ file, line: lineAt(content, match.index), method });
|
|
51
|
+
}
|
|
52
|
+
return facts;
|
|
53
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/arkOrderInvariants.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/ark-order-invariants.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ArkOrderError } from './ark-order-error.mjs';
|
|
12
|
+
import { DEFAULT_MAX_XI_KEYS } from './ark-order-types.mjs';
|
|
13
|
+
import { deterministicHash, stableSerialize } from './stableHash';
|
|
14
|
+
export { DEFAULT_MAX_XI_KEYS };
|
|
15
|
+
const FORBIDDEN_PLANE_METHODS = ['update', 'patch', 'set', 'mutate'];
|
|
16
|
+
export function isForbiddenPlaneMethod(name) {
|
|
17
|
+
return FORBIDDEN_PLANE_METHODS.includes(name);
|
|
18
|
+
}
|
|
19
|
+
function isPrimitive(value) {
|
|
20
|
+
return (value === null ||
|
|
21
|
+
typeof value === 'string' ||
|
|
22
|
+
typeof value === 'number' ||
|
|
23
|
+
typeof value === 'boolean');
|
|
24
|
+
}
|
|
25
|
+
export function freezeRecord(input, label) {
|
|
26
|
+
const keys = Object.keys(input);
|
|
27
|
+
if (keys.length === 0 && label === 'ξ') {
|
|
28
|
+
throw new ArkOrderError('ARKORDER_EMPTY_XI', 'ξ must name at least one slow mode');
|
|
29
|
+
}
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const key of keys.sort()) {
|
|
32
|
+
const value = input[key];
|
|
33
|
+
if (!isPrimitive(value)) {
|
|
34
|
+
throw new ArkOrderError('ARKORDER_NESTED_XI', `${label} key ${JSON.stringify(key)} is not a slow primitive (nested values are microstate)`);
|
|
35
|
+
}
|
|
36
|
+
out[key] = value;
|
|
37
|
+
}
|
|
38
|
+
return Object.freeze(out);
|
|
39
|
+
}
|
|
40
|
+
export function assertXiKeyCap(xi, maxXiKeys) {
|
|
41
|
+
const n = Object.keys(xi).length;
|
|
42
|
+
if (n === 0) {
|
|
43
|
+
throw new ArkOrderError('ARKORDER_EMPTY_XI', 'ξ must name at least one slow mode');
|
|
44
|
+
}
|
|
45
|
+
if (n > maxXiKeys) {
|
|
46
|
+
throw new ArkOrderError('ARKORDER_TOO_MANY_PARAMS', `ξ has ${n} keys; maxXiKeys is ${maxXiKeys} (Haken: few slow modes)`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function assertXiSchema(xi, schema) {
|
|
50
|
+
if (!schema)
|
|
51
|
+
return;
|
|
52
|
+
const properties = schema.properties ?? {};
|
|
53
|
+
const additional = schema.additionalProperties !== false ? true : false;
|
|
54
|
+
for (const key of Object.keys(xi)) {
|
|
55
|
+
const prop = properties[key];
|
|
56
|
+
if (!prop) {
|
|
57
|
+
if (!additional) {
|
|
58
|
+
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} is not in xiSchema.properties`);
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (prop.enum && !prop.enum.some((allowed) => allowed === xi[key])) {
|
|
63
|
+
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} value is not in enum`);
|
|
64
|
+
}
|
|
65
|
+
if (prop.type === 'null' && xi[key] !== null) {
|
|
66
|
+
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} must be null`);
|
|
67
|
+
}
|
|
68
|
+
if (prop.type && prop.type !== 'null' && typeof xi[key] !== prop.type) {
|
|
69
|
+
throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} must be ${prop.type}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function hashReleasePayload(xi, sigma) {
|
|
74
|
+
return deterministicHash(stableSerialize({ xi, sigma }));
|
|
75
|
+
}
|
|
76
|
+
export function createFrozenRelease(input) {
|
|
77
|
+
assertXiKeyCap(input.xi, input.maxXiKeys);
|
|
78
|
+
const xi = freezeRecord(input.xi, 'ξ');
|
|
79
|
+
assertXiSchema(xi, input.xiSchema);
|
|
80
|
+
const sigma = freezeRecord(input.sigma ?? {}, 'σ');
|
|
81
|
+
const release = Object.freeze({
|
|
82
|
+
version: input.version,
|
|
83
|
+
hash: hashReleasePayload(xi, sigma),
|
|
84
|
+
xi,
|
|
85
|
+
sigma,
|
|
86
|
+
releasedAt: input.now,
|
|
87
|
+
});
|
|
88
|
+
return release;
|
|
89
|
+
}
|
|
90
|
+
export function classifyIngest(projection, event, packs = []) {
|
|
91
|
+
const kind = event.kind;
|
|
92
|
+
for (const pack of packs) {
|
|
93
|
+
if (pack.escalateKinds?.includes(kind)) {
|
|
94
|
+
return {
|
|
95
|
+
kind: 'escalate',
|
|
96
|
+
event,
|
|
97
|
+
reason: `pack ${pack.id} slaves kind ${JSON.stringify(kind)} to a pattern change`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (projection.allowedKinds.includes(kind)) {
|
|
102
|
+
return { kind: 'absorb', event };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
kind: 'escalate',
|
|
106
|
+
event,
|
|
107
|
+
reason: `kind ${JSON.stringify(kind)} is not allowed by h(ξ); field cannot rewrite the pattern`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export function blastRadiusOf(previous, next) {
|
|
111
|
+
const prev = new Set(previous.allowedKinds);
|
|
112
|
+
const nxt = new Set(next.allowedKinds);
|
|
113
|
+
const blast = new Set();
|
|
114
|
+
for (const kind of prev) {
|
|
115
|
+
if (!nxt.has(kind))
|
|
116
|
+
blast.add(kind);
|
|
117
|
+
}
|
|
118
|
+
for (const kind of nxt) {
|
|
119
|
+
if (!prev.has(kind))
|
|
120
|
+
blast.add(kind);
|
|
121
|
+
}
|
|
122
|
+
for (const item of next.invalidated)
|
|
123
|
+
blast.add(item);
|
|
124
|
+
return {
|
|
125
|
+
blastRadius: [...blast].sort(),
|
|
126
|
+
invalidations: [...next.invalidated].sort(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export function proposePatternChange(input) {
|
|
130
|
+
const merged = { ...input.current.xi };
|
|
131
|
+
for (const [key, value] of Object.entries(input.delta)) {
|
|
132
|
+
if (value === undefined) {
|
|
133
|
+
delete merged[key];
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
merged[key] = value;
|
|
137
|
+
}
|
|
138
|
+
const candidate = createFrozenRelease({
|
|
139
|
+
xi: merged,
|
|
140
|
+
sigma: { ...input.current.sigma },
|
|
141
|
+
version: input.current.version + 1,
|
|
142
|
+
now: input.now,
|
|
143
|
+
maxXiKeys: input.maxXiKeys,
|
|
144
|
+
xiSchema: input.xiSchema,
|
|
145
|
+
});
|
|
146
|
+
if (candidate.hash === input.current.hash) {
|
|
147
|
+
throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'delta does not change ξ; that is not a pattern change');
|
|
148
|
+
}
|
|
149
|
+
const previous = input.projector(input.current, input.current.sigma);
|
|
150
|
+
const next = input.projector(candidate, candidate.sigma);
|
|
151
|
+
const { blastRadius, invalidations } = blastRadiusOf(previous, next);
|
|
152
|
+
if (blastRadius.length === 0) {
|
|
153
|
+
throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'pattern change has empty blast radius; that key is not an order parameter');
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
nextXi: candidate.xi,
|
|
157
|
+
blastRadius,
|
|
158
|
+
invalidations,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/arkOrderSensors.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/ark-order-sensors.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { extractArkOrderGenericUpdatesFromSource, extractArkOrderPlaneCallsFromSource, isArkOrderModuleSpecifier, } from './ark-order-facts.mjs';
|
|
12
|
+
import { extraMergeTeethAllowed, } from './extra-merge-teeth.mjs';
|
|
13
|
+
import { deterministicNextAction } from './remediation.mjs';
|
|
14
|
+
export const ARKORDER_TIER1_SENSOR_IDS = [
|
|
15
|
+
'arkorder-missing-plane',
|
|
16
|
+
'arkorder-kernel-in-domain',
|
|
17
|
+
'arkorder-generic-update',
|
|
18
|
+
'arkorder-too-many-params',
|
|
19
|
+
'arkorder-ingest-writes-xi',
|
|
20
|
+
];
|
|
21
|
+
export const ARKORDER_RULE_IDS = {
|
|
22
|
+
'arkorder-missing-plane': 'ARKORDER_MISSING_PLANE',
|
|
23
|
+
'arkorder-kernel-in-domain': 'ARKORDER_KERNEL_IN_DOMAIN',
|
|
24
|
+
'arkorder-generic-update': 'ARKORDER_GENERIC_UPDATE',
|
|
25
|
+
'arkorder-too-many-params': 'ARKORDER_TOO_MANY_PARAMS',
|
|
26
|
+
'arkorder-ingest-writes-xi': 'ARKORDER_INGEST_WRITES_XI',
|
|
27
|
+
};
|
|
28
|
+
function isDomainRoleLayer(layer, intentPrefixes = []) {
|
|
29
|
+
const name = layer.trim();
|
|
30
|
+
if (/^domain(?:model)?$/i.test(name) || /^domain(?=[A-Z_\-\s])/i.test(name))
|
|
31
|
+
return true;
|
|
32
|
+
return intentPrefixes.some((prefix) => {
|
|
33
|
+
const normalized = prefix.trim().replace(/\.+$/, '');
|
|
34
|
+
return normalized === 'Domain' || normalized.startsWith('Domain.');
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function finding(extra, sensor, file, line, message, extras, teethAllowed) {
|
|
38
|
+
const failsStrict = extra.mode === 'enforced' && teethAllowed;
|
|
39
|
+
return {
|
|
40
|
+
ruleId: ARKORDER_RULE_IDS[sensor],
|
|
41
|
+
sensor,
|
|
42
|
+
message,
|
|
43
|
+
file,
|
|
44
|
+
line,
|
|
45
|
+
...(extras?.fromLayer ? { fromLayer: extras.fromLayer } : {}),
|
|
46
|
+
...(extras?.target ? { target: extras.target } : {}),
|
|
47
|
+
severity: failsStrict ? 'error' : 'warning',
|
|
48
|
+
failsStrict,
|
|
49
|
+
nextAction: deterministicNextAction({
|
|
50
|
+
ruleId: ARKORDER_RULE_IDS[sensor],
|
|
51
|
+
fromLayer: extras?.fromLayer,
|
|
52
|
+
target: extras?.target,
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function evaluateArkOrderSensors(input) {
|
|
57
|
+
const extra = input.arkOrder;
|
|
58
|
+
if (!extra)
|
|
59
|
+
return { findings: [], completenessReasons: [] };
|
|
60
|
+
const teethAllowed = extraMergeTeethAllowed(input.classification);
|
|
61
|
+
const findings = [];
|
|
62
|
+
const roots = extra.planeRoots;
|
|
63
|
+
if (extra.mode === 'enforced' && roots.length === 0) {
|
|
64
|
+
findings.push(finding(extra, 'arkorder-missing-plane', 'ark.config.json', 1, 'ArkOrder planeRoots is empty; no createOrderPlane site is declared.', undefined, teethAllowed));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const hitsByRoot = new Map();
|
|
68
|
+
for (const hit of input.planeRootHits) {
|
|
69
|
+
const list = hitsByRoot.get(hit.matchedRoot) ?? [];
|
|
70
|
+
list.push(hit);
|
|
71
|
+
hitsByRoot.set(hit.matchedRoot, list);
|
|
72
|
+
}
|
|
73
|
+
for (const pattern of roots) {
|
|
74
|
+
const matched = hitsByRoot.get(pattern) ?? [];
|
|
75
|
+
if (matched.length === 0) {
|
|
76
|
+
findings.push(finding(extra, 'arkorder-missing-plane', 'ark.config.json', 1, `ArkOrder plane root ${JSON.stringify(pattern)} matched no governed files and has no createOrderPlane factory.`, { target: pattern }, teethAllowed));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (matched.some((hit) => hit.hasPlaneFactory))
|
|
80
|
+
continue;
|
|
81
|
+
findings.push(finding(extra, 'arkorder-missing-plane', matched[0].file, 1, `ArkOrder plane root ${JSON.stringify(pattern)} has no createOrderPlane factory.`, { target: pattern }, teethAllowed));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const prefixes = new Map(input.layers.map((layer) => [layer.name, layer.intentPrefixes ?? []]));
|
|
85
|
+
for (const dependency of input.dependencies) {
|
|
86
|
+
const specifier = dependency.specifier;
|
|
87
|
+
if (!specifier || !isArkOrderModuleSpecifier(specifier))
|
|
88
|
+
continue;
|
|
89
|
+
const fromLayer = input.layerForFile(dependency.from);
|
|
90
|
+
if (!fromLayer)
|
|
91
|
+
continue;
|
|
92
|
+
if (!isDomainRoleLayer(fromLayer, prefixes.get(fromLayer) ?? []))
|
|
93
|
+
continue;
|
|
94
|
+
findings.push(finding(extra, 'arkorder-kernel-in-domain', dependency.from, dependency.line, 'Domain-role layer imports arkgate/order; Domain stays plane-free.', { fromLayer, target: specifier }, teethAllowed));
|
|
95
|
+
}
|
|
96
|
+
for (const update of input.genericUpdates) {
|
|
97
|
+
findings.push(finding(extra, 'arkorder-generic-update', update.file, update.line, `Generic ${update.method}() on the order plane rewrites ξ; Haken forbids it.`, { target: update.method }, teethAllowed));
|
|
98
|
+
}
|
|
99
|
+
findings.sort((left, right) => left.file.localeCompare(right.file) ||
|
|
100
|
+
left.ruleId.localeCompare(right.ruleId) ||
|
|
101
|
+
left.line - right.line);
|
|
102
|
+
return { findings, completenessReasons: [] };
|
|
103
|
+
}
|
|
104
|
+
export function evaluateArkOrderEditorSensors(input) {
|
|
105
|
+
if (!input.arkOrder)
|
|
106
|
+
return [];
|
|
107
|
+
const planeCalls = extractArkOrderPlaneCallsFromSource(input.file, input.source);
|
|
108
|
+
const genericUpdates = extractArkOrderGenericUpdatesFromSource(input.file, input.source);
|
|
109
|
+
return evaluateArkOrderSensors({
|
|
110
|
+
arkOrder: input.arkOrder,
|
|
111
|
+
layers: [],
|
|
112
|
+
planeCalls,
|
|
113
|
+
genericUpdates,
|
|
114
|
+
planeRootHits: [],
|
|
115
|
+
dependencies: [],
|
|
116
|
+
layerForFile: () => input.fromLayer,
|
|
117
|
+
}).findings.filter((item) => item.sensor === 'arkorder-generic-update' || item.sensor === 'arkorder-kernel-in-domain');
|
|
118
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/arkOrderTypes.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/ark-order-types.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_MAX_XI_KEYS = 7;
|
|
@@ -111,9 +111,9 @@ function dependKinds(kind) {
|
|
|
111
111
|
}
|
|
112
112
|
function evaluateMissingRoot(extra, hits, teethAllowed) {
|
|
113
113
|
const out = [];
|
|
114
|
-
const roots = extra.compositionRoots;
|
|
114
|
+
const roots = extra.kernelRoots ?? extra.compositionRoots;
|
|
115
115
|
if (roots.length === 0) {
|
|
116
|
-
out.push(finding(extra, 'arkrun-missing-root', 'ark.config.json', 1, 'ArkRun
|
|
116
|
+
out.push(finding(extra, 'arkrun-missing-root', 'ark.config.json', 1, 'ArkRun kernelRoots is empty; no createArkKernel factory site is declared.', undefined, teethAllowed));
|
|
117
117
|
return out;
|
|
118
118
|
}
|
|
119
119
|
const hitsByRoot = new Map();
|
|
@@ -125,14 +125,14 @@ function evaluateMissingRoot(extra, hits, teethAllowed) {
|
|
|
125
125
|
for (const pattern of roots) {
|
|
126
126
|
const matched = [...(hitsByRoot.get(pattern) ?? [])].sort((left, right) => left.file.localeCompare(right.file));
|
|
127
127
|
if (matched.length === 0) {
|
|
128
|
-
out.push(finding(extra, 'arkrun-missing-root', 'ark.config.json', 1, `ArkRun
|
|
128
|
+
out.push(finding(extra, 'arkrun-missing-root', 'ark.config.json', 1, `ArkRun kernel root ${JSON.stringify(pattern)} matched no governed files and has no createArkKernel factory.`, { target: pattern }, teethAllowed));
|
|
129
129
|
continue;
|
|
130
130
|
}
|
|
131
131
|
// Factory required in the root set, not in every glob hit.
|
|
132
132
|
if (matched.some((hit) => hit.hasKernelFactory))
|
|
133
133
|
continue;
|
|
134
134
|
const first = matched[0];
|
|
135
|
-
out.push(finding(extra, 'arkrun-missing-root', first.file, 1, `ArkRun
|
|
135
|
+
out.push(finding(extra, 'arkrun-missing-root', first.file, 1, `ArkRun kernel root ${JSON.stringify(pattern)} has no createArkKernel / createStrictArkKernel factory.`, { target: pattern }, teethAllowed));
|
|
136
136
|
}
|
|
137
137
|
return out;
|
|
138
138
|
}
|
|
@@ -162,6 +162,13 @@ function evaluateDirectNew(extra, layers, managedNews, hits, layerForFile, teeth
|
|
|
162
162
|
for (const constructed of managedNews) {
|
|
163
163
|
if (admittedFactories.has(constructed.file))
|
|
164
164
|
continue;
|
|
165
|
+
const name = constructed.typeName;
|
|
166
|
+
if (extra.ignoreDirectNewForErrors !== false && (name.endsWith('Error') || name === 'Error')) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (name.endsWith('DTO') || name.endsWith('VO')) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
165
172
|
const fromLayer = layerForFile(constructed.file);
|
|
166
173
|
if (!fromLayer || !managed.has(fromLayer))
|
|
167
174
|
continue;
|
|
@@ -279,7 +286,8 @@ function fileMatchesCompositionRoot(pattern, file) {
|
|
|
279
286
|
function compositionRootHitsForSource(extra, file, source) {
|
|
280
287
|
const hasFactory = extractArkRunKernelCallsFromSource(file, source).some((call) => call.kind === 'factory');
|
|
281
288
|
const hits = [];
|
|
282
|
-
|
|
289
|
+
const roots = extra.kernelRoots ?? extra.compositionRoots;
|
|
290
|
+
for (const pattern of roots) {
|
|
283
291
|
if (!fileMatchesCompositionRoot(pattern, file))
|
|
284
292
|
continue;
|
|
285
293
|
hits.push({ file, matchedRoot: pattern, hasKernelFactory: hasFactory });
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* Pure CLI helper (bin/lib/config-contract.mjs). Zero Node I/O.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import { ARK_ORDER_SCHEMA_DEF, ARK_RUN_SCHEMA_DEF, defaultedArkOrder, defaultedArkRun, validateArkOrderExtra, validateArkRunExtra, } from './config-extras.mjs';
|
|
12
|
+
/** Current published ark.config.json schema version (ADR 0027: 1.3 adds optional arkOrder). */
|
|
13
|
+
export const ARK_CONFIG_SCHEMA_VERSION = '1.3';
|
|
13
14
|
export const ARK_CONFIG_SCHEMA_URL = 'https://unpkg.com/arkgate@2/schemas/ark.config.schema.json';
|
|
14
15
|
const DEFAULT_LAYER_NAMES = [
|
|
15
16
|
'DomainModel',
|
|
@@ -51,6 +52,7 @@ export const ARK_CONFIG_MIGRATIONS = [
|
|
|
51
52
|
{ from: 'unversioned', to: '1.0' },
|
|
52
53
|
{ from: '1.0', to: '1.1' },
|
|
53
54
|
{ from: '1.1', to: '1.2' },
|
|
55
|
+
{ from: '1.2', to: '1.3' },
|
|
54
56
|
];
|
|
55
57
|
const stringArraySchema = {
|
|
56
58
|
type: 'array',
|
|
@@ -82,11 +84,7 @@ export const ARK_CONFIG_SCHEMA = {
|
|
|
82
84
|
exclude: { ...stringArraySchema, default: [] },
|
|
83
85
|
excludeGenerated: { type: 'boolean', default: true },
|
|
84
86
|
frameworkOverlay: { type: 'string', minLength: 1 },
|
|
85
|
-
layers: {
|
|
86
|
-
type: 'array',
|
|
87
|
-
default: [],
|
|
88
|
-
items: { $ref: '#/$defs/layer' },
|
|
89
|
-
},
|
|
87
|
+
layers: { type: 'array', default: [], items: { $ref: '#/$defs/layer' } },
|
|
90
88
|
rules: {
|
|
91
89
|
type: 'array',
|
|
92
90
|
default: DEFAULT_ARK_CONFIG_RULES,
|
|
@@ -115,6 +113,8 @@ export const ARK_CONFIG_SCHEMA = {
|
|
|
115
113
|
},
|
|
116
114
|
/** ADR 0020 — optional ArkRun extra. Absence is silent; unknown keys fail closed. */
|
|
117
115
|
arkRun: { $ref: '#/$defs/arkRun' },
|
|
116
|
+
/** ADR 0027 — optional ArkOrder extra. Absence is silent; unknown keys fail closed. */
|
|
117
|
+
arkOrder: { $ref: '#/$defs/arkOrder' },
|
|
118
118
|
/** Team parliament — GitHub handles or emails who may loosen the law (not part of policy hash). */
|
|
119
119
|
stewards: { ...stringArraySchema, default: [] },
|
|
120
120
|
},
|
|
@@ -184,20 +184,8 @@ export const ARK_CONFIG_SCHEMA = {
|
|
|
184
184
|
allowDisabledPeerIsolation: { type: 'boolean', default: false },
|
|
185
185
|
},
|
|
186
186
|
},
|
|
187
|
-
arkRun:
|
|
188
|
-
|
|
189
|
-
additionalProperties: false,
|
|
190
|
-
properties: {
|
|
191
|
-
mode: {
|
|
192
|
-
type: 'string',
|
|
193
|
-
enum: ['advisory', 'enforced'],
|
|
194
|
-
default: 'advisory',
|
|
195
|
-
},
|
|
196
|
-
compositionRoots: { ...stringArraySchema, default: [] },
|
|
197
|
-
managedLayers: { ...stringArraySchema, default: [] },
|
|
198
|
-
requireDeclarations: { type: 'boolean', default: true },
|
|
199
|
-
},
|
|
200
|
-
},
|
|
187
|
+
arkRun: ARK_RUN_SCHEMA_DEF,
|
|
188
|
+
arkOrder: ARK_ORDER_SCHEMA_DEF,
|
|
201
189
|
},
|
|
202
190
|
};
|
|
203
191
|
export class ArkConfigValidationError extends Error {
|
|
@@ -331,17 +319,6 @@ function validateNode(value, schema, path, root, issues) {
|
|
|
331
319
|
}
|
|
332
320
|
}
|
|
333
321
|
}
|
|
334
|
-
function defaultedArkRun(value) {
|
|
335
|
-
if (!isObject(value))
|
|
336
|
-
return value;
|
|
337
|
-
return {
|
|
338
|
-
...value,
|
|
339
|
-
mode: value.mode === undefined ? 'advisory' : value.mode,
|
|
340
|
-
compositionRoots: value.compositionRoots === undefined ? [] : value.compositionRoots,
|
|
341
|
-
managedLayers: value.managedLayers === undefined ? [] : value.managedLayers,
|
|
342
|
-
requireDeclarations: value.requireDeclarations === undefined ? true : value.requireDeclarations,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
322
|
function defaultedConfig(input) {
|
|
346
323
|
const result = {
|
|
347
324
|
...input,
|
|
@@ -355,54 +332,20 @@ function defaultedConfig(input) {
|
|
|
355
332
|
};
|
|
356
333
|
if (input.arkRun !== undefined)
|
|
357
334
|
result.arkRun = defaultedArkRun(input.arkRun);
|
|
335
|
+
if (input.arkOrder !== undefined)
|
|
336
|
+
result.arkOrder = defaultedArkOrder(input.arkOrder);
|
|
358
337
|
return result;
|
|
359
338
|
}
|
|
360
|
-
function validateArkRunExtra(config, issues) {
|
|
361
|
-
const extra = config.arkRun;
|
|
362
|
-
if (extra === undefined || !isObject(extra))
|
|
363
|
-
return;
|
|
364
|
-
const layerNames = new Set();
|
|
365
|
-
if (Array.isArray(config.layers)) {
|
|
366
|
-
for (const layer of config.layers) {
|
|
367
|
-
if (isObject(layer) && typeof layer.name === 'string' && layer.name.length > 0) {
|
|
368
|
-
layerNames.add(layer.name);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const managed = extra.managedLayers;
|
|
373
|
-
if (Array.isArray(managed)) {
|
|
374
|
-
managed.forEach((name, index) => {
|
|
375
|
-
if (typeof name === 'string' && name.length > 0 && !layerNames.has(name)) {
|
|
376
|
-
issues.push({
|
|
377
|
-
path: `$.arkRun.managedLayers[${index}]`,
|
|
378
|
-
message: `layer ${JSON.stringify(name)} is not declared in layers[]`,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
if (extra.mode === 'enforced') {
|
|
384
|
-
const roots = extra.compositionRoots;
|
|
385
|
-
if (!Array.isArray(roots) || roots.length === 0) {
|
|
386
|
-
issues.push({
|
|
387
|
-
path: '$.arkRun.compositionRoots',
|
|
388
|
-
message: 'ARKRUN_MISSING_ROOT: enforced mode requires at least one composition root',
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
if (!Array.isArray(managed) || managed.length === 0) {
|
|
392
|
-
issues.push({
|
|
393
|
-
path: '$.arkRun.managedLayers',
|
|
394
|
-
message: 'enforced mode requires at least one managed layer',
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
339
|
function migratedFromOf(originalVersion) {
|
|
400
340
|
if (originalVersion === ARK_CONFIG_SCHEMA_VERSION)
|
|
401
341
|
return null;
|
|
402
342
|
if (originalVersion === 'unversioned')
|
|
403
343
|
return 'unversioned';
|
|
404
|
-
if (originalVersion === '1.0' ||
|
|
344
|
+
if (originalVersion === '1.0' ||
|
|
345
|
+
originalVersion === '1.1' ||
|
|
346
|
+
originalVersion === '1.2') {
|
|
405
347
|
return originalVersion;
|
|
348
|
+
}
|
|
406
349
|
return null;
|
|
407
350
|
}
|
|
408
351
|
function knownInputVersions() {
|
|
@@ -480,6 +423,7 @@ export function loadArkConfigContract(input, source = 'ark.config.json') {
|
|
|
480
423
|
const issues = [];
|
|
481
424
|
validateNode(candidate, ARK_CONFIG_SCHEMA, '$', ARK_CONFIG_SCHEMA, issues);
|
|
482
425
|
validateArkRunExtra(candidate, issues);
|
|
426
|
+
validateArkOrderExtra(candidate, issues);
|
|
483
427
|
if (issues.length > 0)
|
|
484
428
|
throw new ArkConfigValidationError(source, issues);
|
|
485
429
|
return { config: candidate, migratedFrom };
|