arkgate 4.8.11 → 4.8.13

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +43 -2
  2. package/README.md +22 -32
  3. package/bin/ark-check-runtime.mjs +2 -0
  4. package/bin/lib/analysis-engine.mjs +4 -4
  5. package/bin/lib/ark-order-doctor.mjs +154 -0
  6. package/bin/lib/ark-order-report.mjs +64 -0
  7. package/bin/lib/ark-order-sensors.mjs +1 -1
  8. package/bin/lib/diagnostic-catalog.mjs +2 -2
  9. package/bin/lib/doctor-advisories.mjs +91 -18
  10. package/bin/lib/doctor-human.mjs +2 -10
  11. package/bin/lib/doctor-plan.mjs +4 -3
  12. package/bin/lib/extra-merge-teeth.mjs +32 -4
  13. package/bin/lib/html-report-advisories.mjs +2 -0
  14. package/bin/lib/html-report-depth.mjs +8 -18
  15. package/bin/lib/html-report.mjs +16 -0
  16. package/bin/lib/remediation.mjs +5 -5
  17. package/bin/lib/rules-under-contract.mjs +14 -0
  18. package/bin/lib/start-preview.mjs +1 -0
  19. package/bin/lib/status-command.mjs +28 -0
  20. package/bin/lib/status-manifest.mjs +23 -0
  21. package/bin/lib/violations.mjs +10 -1
  22. package/dist/{diagnosticCatalog-DiflIock.d.ts → diagnosticCatalog-DA565Lja.d.ts} +1 -1
  23. package/dist/eslint/index.cjs +4 -4
  24. package/dist/eslint/index.js +4 -4
  25. package/dist/index.cjs +31 -31
  26. package/dist/index.d.ts +71 -3
  27. package/dist/index.js +31 -31
  28. package/dist/nestjs/index.cjs +1 -1
  29. package/dist/nestjs/index.js +1 -1
  30. package/dist/runtime/index.cjs +15 -15
  31. package/dist/runtime/index.d.ts +1 -1
  32. package/dist/runtime/index.js +15 -15
  33. package/docs/README.md +4 -3
  34. package/docs/agent-guide.md +2 -1
  35. package/docs/arkorder.md +28 -10
  36. package/docs/package-surface.md +2 -1
  37. package/docs/product-voice.md +9 -8
  38. package/docs/use.md +1 -1
  39. package/package.json +1 -1
  40. package/schemas/ark.status-manifest.schema.json +47 -0
  41. package/server.json +2 -2
@@ -0,0 +1,154 @@
1
+ /**
2
+ * GENERATED FILE — do not edit by hand.
3
+ *
4
+ * Canonical algorithm: src/domain/arkOrderDoctor.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-doctor.mjs). Zero Node I/O.
9
+ */
10
+
11
+ import { composeMergePlanesHonesty, extraMergeTeethAllowed, isArkOrderRuleId, } from './extra-merge-teeth.mjs';
12
+ export const ARK_ORDER_DOCTOR_SCHEMA_VERSION = '1.0';
13
+ const RESIDUAL_RULE_CAP = 12;
14
+ export const ARKORDER_ONE_BREATH = 'Layers stop a bad import. ArkOrder stops rewriting a big product choice — like the billing plan — as if it were a seat count. Change those choices through a valve, not a generic update.';
15
+ function closedMode(value) {
16
+ return value === 'enforced' || value === 'advisory' ? value : null;
17
+ }
18
+ function uniqueArkOrderRuleIds(findings) {
19
+ const seen = new Set();
20
+ if (!Array.isArray(findings))
21
+ return [];
22
+ for (const finding of findings) {
23
+ const id = finding?.ruleId;
24
+ if (typeof id !== 'string' || !isArkOrderRuleId(id) || seen.has(id))
25
+ continue;
26
+ seen.add(id);
27
+ }
28
+ return [...seen].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
29
+ }
30
+ function extraFromConfig(arkOrder) {
31
+ if (!arkOrder || typeof arkOrder !== 'object') {
32
+ return { present: false, mode: null, roots: 0, layers: 0, xiKeys: [] };
33
+ }
34
+ const xiKeys = Array.isArray(arkOrder.xiKeys)
35
+ ? arkOrder.xiKeys.filter((key) => typeof key === 'string' && key.length > 0)
36
+ : [];
37
+ return {
38
+ present: true,
39
+ mode: closedMode(arkOrder.mode),
40
+ roots: Array.isArray(arkOrder.planeRoots) ? arkOrder.planeRoots.length : 0,
41
+ layers: Array.isArray(arkOrder.managedLayers) ? arkOrder.managedLayers.length : 0,
42
+ xiKeys,
43
+ };
44
+ }
45
+ /**
46
+ * Doctor / HTML ArkOrder advisory. Always emitted; absence is an honest silent row.
47
+ */
48
+ export function summarizeArkOrderSection(input = {}) {
49
+ const extra = extraFromConfig(input.arkOrder);
50
+ const uniqueIds = extra.present ? uniqueArkOrderRuleIds(input.findings) : [];
51
+ const ruleIds = uniqueIds.slice(0, RESIDUAL_RULE_CAP);
52
+ const residualCount = uniqueIds.length;
53
+ const mergePlanes = composeMergePlanesHonesty({
54
+ classification: input.classification,
55
+ arkRules: {
56
+ active: input.arkRules?.active === true,
57
+ structureEnforced: input.arkRules?.structureEnforced,
58
+ structureTotal: input.arkRules?.structureTotal,
59
+ structureAdvisory: input.arkRules?.structureAdvisory,
60
+ invariantEnforced: input.arkRules?.invariantEnforced,
61
+ invariantTotal: input.arkRules?.invariantTotal,
62
+ invariantAdvisory: input.arkRules?.invariantAdvisory,
63
+ covered: input.arkRules?.covered,
64
+ uncovered: input.arkRules?.uncovered,
65
+ },
66
+ arkRun: {
67
+ present: input.arkRun?.present === true,
68
+ mode: input.arkRun?.mode ?? null,
69
+ residualCount: input.arkRun?.residualCount,
70
+ },
71
+ arkOrder: {
72
+ present: extra.present,
73
+ mode: extra.mode,
74
+ residualCount,
75
+ },
76
+ });
77
+ const extraMergeTeeth = extra.present && extra.mode === 'enforced' && extraMergeTeethAllowed(input.classification);
78
+ let note;
79
+ if (!extra.present) {
80
+ note = 'Absence of arkOrder is silent — Layers verdicts unchanged. Not a score.';
81
+ }
82
+ else if (extra.mode === 'advisory') {
83
+ note =
84
+ 'Advisory ArkOrder residual only — never flips valid or --strict-merge. Residual is a finding-id count, never a score.';
85
+ }
86
+ else if (extraMergeTeeth) {
87
+ note =
88
+ 'Enforced ArkOrder is on the extra merge plane. Residual is a finding-id count, never a score.';
89
+ }
90
+ else {
91
+ note =
92
+ 'Enforced ArkOrder extra teeth stay demoted until the layer plane is honestly classified. Residual is a finding-id count, never a score.';
93
+ }
94
+ return {
95
+ schemaVersion: ARK_ORDER_DOCTOR_SCHEMA_VERSION,
96
+ notAScore: true,
97
+ active: extra.present,
98
+ mode: extra.mode,
99
+ planeRoots: extra.roots,
100
+ managedLayers: extra.layers,
101
+ xiKeys: extra.xiKeys,
102
+ residual: { count: residualCount, ruleIds },
103
+ extraMergeTeeth,
104
+ failMergeWhen: mergePlanes.failMergeWhen,
105
+ note,
106
+ mergePlanes,
107
+ };
108
+ }
109
+ /** Thin status slice — counts only; residual null means unknown, not green. */
110
+ export function projectStatusArkOrder(input = {}) {
111
+ const present = input.present === true;
112
+ const mode = closedMode(input.mode);
113
+ const residualRaw = input.residual;
114
+ let residual = null;
115
+ if (typeof residualRaw === 'number' && Number.isFinite(residualRaw) && residualRaw >= 0) {
116
+ residual = Math.floor(residualRaw);
117
+ }
118
+ if (!present)
119
+ residual = residual == null ? 0 : residual;
120
+ return {
121
+ notAScore: true,
122
+ present,
123
+ mode: present ? mode : null,
124
+ extraMergeTeeth: present && mode === 'enforced' && input.extraMergeTeeth === true,
125
+ residual,
126
+ };
127
+ }
128
+ export function formatArkOrderDoctorLines(section) {
129
+ if (!section || section.notAScore !== true)
130
+ return [];
131
+ if (section.active !== true) {
132
+ return ['ArkOrder extra is off — silent on Layers (not a score).'];
133
+ }
134
+ const mode = section.mode ?? 'unknown';
135
+ const teeth = section.extraMergeTeeth === true ? 'armed' : 'not armed';
136
+ const keys = section.xiKeys.length > 0 ? section.xiKeys.join(', ') : '(none named — field-write sensor silent)';
137
+ const lines = [
138
+ ARKORDER_ONE_BREATH,
139
+ `mode: ${mode} · xiKeys: ${keys} · extra merge teeth ${teeth} · not a score`,
140
+ ];
141
+ if (section.residual.count > 0) {
142
+ const shown = section.residual.ruleIds.join(', ');
143
+ const more = section.residual.count > section.residual.ruleIds.length
144
+ ? ` (+${section.residual.count - section.residual.ruleIds.length} more)`
145
+ : '';
146
+ lines.push(`Residual: ${shown}${more}`);
147
+ }
148
+ else {
149
+ lines.push('Residual: none on this scan (not a score — green extras ≠ a frozen billing plan).');
150
+ }
151
+ if (section.failMergeWhen)
152
+ lines.push(section.failMergeWhen);
153
+ return lines;
154
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * HTML for the doctor ArkOrder advisory (report parity: data-advisory="arkOrder").
3
+ */
4
+ import { ARKORDER_ONE_BREATH } from './ark-order-doctor.mjs';
5
+
6
+ export function formatArkOrderHtml(section, esc) {
7
+ if (!section || typeof section !== 'object' || section.notAScore !== true) return '';
8
+ const escape = typeof esc === 'function' ? esc : (v) => String(v);
9
+ const note = section.note ? `<p class="muted">${escape(section.note)}</p>` : '';
10
+ if (section.active !== true) {
11
+ return `
12
+ <section class="section card" data-advisory="arkOrder">
13
+ <h2>ArkOrder <span class="muted">(opt-in extra — not a score)</span></h2>
14
+ <p class="dim" style="margin:.15rem 0 .55rem;font-size:.88rem">
15
+ ${ARKORDER_ONE_BREATH}
16
+ Off until you turn it on — Layers stay the same.
17
+ </p>
18
+ ${note}
19
+ </section>`;
20
+ }
21
+ const residual = section.residual && typeof section.residual === 'object' ? section.residual : { count: 0, ruleIds: [] };
22
+ const ids = Array.isArray(residual.ruleIds) ? residual.ruleIds : [];
23
+ const residualHtml =
24
+ residual.count > 0
25
+ ? `<p><span class="tag warn">residual</span> ${ids
26
+ .map((id) => `<code>${escape(id)}</code>`)
27
+ .join(' · ')}${
28
+ residual.count > ids.length ? ` <span class="muted">(+${residual.count - ids.length} more)</span>` : ''
29
+ }</p>`
30
+ : '<p class="muted">Residual: none on this scan (not a score — green extras ≠ a frozen billing plan).</p>';
31
+ const teeth = section.extraMergeTeeth === true
32
+ ? '<span class="tag">extra merge teeth armed</span>'
33
+ : '<span class="tag warn">extra merge teeth not armed</span>';
34
+ const mergeSentence =
35
+ section.mergePlanes && typeof section.mergePlanes.failMergeWhen === 'string'
36
+ ? section.mergePlanes.failMergeWhen
37
+ : section.failMergeWhen;
38
+ const merge =
39
+ mergeSentence
40
+ ? `<p class="muted" style="margin:.35rem 0 .55rem;font-size:.86rem"><b>Merge planes:</b> ${escape(mergeSentence)}</p>`
41
+ : '';
42
+ const keys = Array.isArray(section.xiKeys) && section.xiKeys.length > 0
43
+ ? section.xiKeys.map((key) => `<code>${escape(key)}</code>`).join(' · ')
44
+ : '<span class="muted">(none named — field-write sensor silent)</span>';
45
+ return `
46
+ <section class="section card" data-advisory="arkOrder">
47
+ <h2>ArkOrder <span class="muted">(not a score)</span></h2>
48
+ <p class="dim" style="margin:.15rem 0 .55rem;font-size:.88rem">
49
+ <b>[ArkOrder]</b> ${ARKORDER_ONE_BREATH}
50
+ Separate from <b>[Layer]</b> imports, <b>[ArkRules]</b> shape, and <b>[ArkRun]</b> travel.
51
+ </p>
52
+ ${merge}
53
+ <div class="kpis" style="margin-bottom:.55rem">
54
+ <div class="kpi"><b>${escape(section.mode || '—')}</b><span>Mode</span></div>
55
+ <div class="kpi"><b>${Number(residual.count) || 0}</b><span>Residual ids</span></div>
56
+ <div class="kpi"><b>${Number(section.planeRoots) || 0}</b><span>Plane roots</span></div>
57
+ <div class="kpi"><b>${Number(section.managedLayers) || 0}</b><span>Managed layers</span></div>
58
+ </div>
59
+ <p>${teeth} · <code>notAScore</code></p>
60
+ <p class="dim" style="margin:.35rem 0 .55rem;font-size:.86rem">xiKeys: ${keys}</p>
61
+ ${residualHtml}
62
+ ${note}
63
+ </section>`;
64
+ }
@@ -219,7 +219,7 @@ export function evaluateArkOrderSensors(input) {
219
219
  continue;
220
220
  if (!matchesArkOrderAppliesTo(write.file, extra.appliesTo))
221
221
  continue;
222
- findings.push(finding(extra, 'arkorder-xi-field-write', write.file, write.line, `File writes slow key ${JSON.stringify(write.key)} through a persistence driver; route the field through ingest or a pattern change through proposeRelease.`, { fromLayer, target: write.key }, teethAllowed));
222
+ findings.push(finding(extra, 'arkorder-xi-field-write', write.file, write.line, `This file writes ${JSON.stringify(write.key)} the same way it would write a seat count. Take the event in, or change that choice through the valve (propose, then apply).`, { fromLayer, target: write.key }, teethAllowed));
223
223
  }
224
224
  findings.sort((left, right) => left.file.localeCompare(right.file) ||
225
225
  left.ruleId.localeCompare(right.ruleId) ||
@@ -68,10 +68,10 @@ export const DIAGNOSTIC_CATALOG = Object.freeze([
68
68
  entry('ARKRUN_TRANSPORT_BYPASS', 'arkrun', 'Homemade broker or emitter import', 'A managed layer imports a closed broker/queue/emitter specifier (EventEmitter, queue clients, …) instead of the ArkRun kernel transport.', 'Send through the ArkRun kernel transport instead of importing that broker or emitter, then preflight again. Never mechanical-safe — homemade buses stay judgment.'),
69
69
  entry('ARKORDER_MISSING_PLANE', 'arkorder', 'No createOrderPlane in plane roots', 'The ArkOrder extra is on but no createOrderPlane factory was found in arkOrder.planeRoots, so agents can skip the pattern plane while the write gate stays green.', 'Import createOrderPlane from arkgate/order and call it in a plane root listed in arkOrder.planeRoots, then preflight again. Never mechanical-safe — factory placement is a design decision.'),
70
70
  entry('ARKORDER_KERNEL_IN_DOMAIN', 'arkorder', 'Domain-role layer imports the order plane', 'A Domain-role layer imports arkgate/order. Domain stays plane-free; planeRoots own the factory.', 'Move the arkgate/order import out of the Domain-role layer into a plane root or adapter, then preflight again. Never mechanical-safe.'),
71
- entry('ARKORDER_GENERIC_UPDATE', 'arkorder', 'Generic update of ξ', 'A call to update/patch/set on the order plane rewrites the slow pattern. Haken slaving forbids generic ξ mutation.', 'Use release() for the first freeze of ξ. Later pattern change is proposeRelease then apply(ProposeResult). Never update/patch/set. Never mechanical-safe.'),
71
+ entry('ARKORDER_GENERIC_UPDATE', 'arkorder', 'Generic update of a big product choice', 'A call to update/patch/set rewrites a named product choice (like billing plan) as if it were a seat count.', 'Don\'t use a generic update. First freeze with release(). Later, propose the change, then apply it.'),
72
72
  entry('ARKORDER_TOO_MANY_PARAMS', 'arkorder', 'Too many slow keys', 'ξ has more keys than arkOrder.maxXiKeys. Haken requires a few slow modes, not a dump of microstate.', 'Cut ξ to the slow keys that actually slave the rest, then preflight again. Never mechanical-safe.'),
73
73
  entry('ARKORDER_INGEST_WRITES_XI', 'arkorder', 'ingest assigned into ξ', 'An ingest() result is written into a Release or ξ store. ingest may absorb, escalate_up, or hold; it never mints a pattern.', 'Keep ingest results as absorb/escalate_up/hold only. Change ξ with proposeRelease then apply(ProposeResult). Never mechanical-safe.'),
74
- entry('ARKORDER_XI_FIELD_WRITE', 'arkorder', 'Slow key written around the order plane', 'A managed-layer file imports a persistence driver and writes a declared arkOrder.xiKeys name. Field events absorb or escalate; they do not PATCH the slow pattern.', 'Keep invoices, seats, hours, and logs on ingest. Change the slow key with proposeRelease then apply(ProposeResult), then preflight again. Never mechanical-safe.'),
74
+ entry('ARKORDER_XI_FIELD_WRITE', 'arkorder', 'Big product choice written like a seat count', 'A use-case writes a named product choice (like billing plan) through a database update. Invoices and seats can flow. That choice cannot.', 'Keep invoices and seats as events. Change the choice through the valve (proposeRelease then apply), not a generic update.'),
75
75
  entry('ARKORDER_INFORMATION_BUDGET', 'arkorder', 'Projection observes a forbidden kind', 'h(ξ) allowedKinds includes a kind listed in informationBudget.cannotObserve. A scale may not look at what it was told not to see.', 'Cut that kind from the projector or from cannotObserve, then preflight again. Never mechanical-safe.'),
76
76
  entry('ARKORDER_XI_TTL', 'arkorder', 'Slow key carries a freshness field', 'ξ named ttl/freshUntil/maxAge. Freshness belongs on σ. A slow parameter that expires per transaction is not slow.', 'Move freshness onto σ (freshUntil) and keep ξ stable, then preflight again. Never mechanical-safe.'),
77
77
  entry('ARKORDER_STALE_SIGMA', 'arkorder', 'σ is stale', 'ingest ran after σ.freshUntil (or sigmaMaxAgeMs). ξ does not TTL.', 'Call refreshSigma and ingest again, or proposeRelease then apply(ProposeResult) if the pattern changed. Never mechanical-safe.'),
@@ -21,6 +21,82 @@ import { detectGraphBlindSpots, printGraphBlindSection } from './graph-blind.mjs
21
21
  import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
22
22
  import { collectStewardNudge } from './team-parliament-io.mjs';
23
23
  import { formatArkRunDoctorLines, summarizeArkRunSection } from './ark-run-doctor.mjs';
24
+ import {
25
+ ARKORDER_ONE_BREATH,
26
+ formatArkOrderDoctorLines,
27
+ summarizeArkOrderSection,
28
+ } from './ark-order-doctor.mjs';
29
+ import { composeMergePlanesHonesty } from './extra-merge-teeth.mjs';
30
+
31
+ export function attachExtraDoctorSections(rulesUnderContract, config, classification, findings) {
32
+ const arkRulesMerge = {
33
+ active: rulesUnderContract?.active === true,
34
+ structureEnforced: rulesUnderContract?.mergePlanes?.structureSensors?.enforced,
35
+ structureTotal: rulesUnderContract?.mergePlanes?.structureSensors?.total,
36
+ structureAdvisory: rulesUnderContract?.mergePlanes?.structureSensors?.advisory,
37
+ invariantEnforced: rulesUnderContract?.mergePlanes?.invariants?.enforced,
38
+ invariantTotal: rulesUnderContract?.mergePlanes?.invariants?.total,
39
+ invariantAdvisory: rulesUnderContract?.mergePlanes?.invariants?.advisory,
40
+ covered: rulesUnderContract?.mergePlanes?.invariants?.covered,
41
+ uncovered: rulesUnderContract?.mergePlanes?.invariants?.uncovered,
42
+ };
43
+ const arkRun = summarizeArkRunSection({
44
+ arkRun: config?.arkRun,
45
+ findings,
46
+ classification,
47
+ arkRules: arkRulesMerge,
48
+ });
49
+ const arkOrder = summarizeArkOrderSection({
50
+ arkOrder: config?.arkOrder,
51
+ findings,
52
+ classification,
53
+ arkRules: arkRulesMerge,
54
+ arkRun: {
55
+ present: arkRun.active === true,
56
+ mode: arkRun.mode,
57
+ residualCount: arkRun.residual?.count,
58
+ },
59
+ });
60
+ const mergePlanes = composeMergePlanesHonesty({
61
+ classification,
62
+ arkRules: arkRulesMerge,
63
+ arkRun: {
64
+ present: arkRun.active === true,
65
+ mode: arkRun.mode,
66
+ residualCount: arkRun.residual?.count,
67
+ },
68
+ arkOrder: {
69
+ present: arkOrder.active === true,
70
+ mode: arkOrder.mode,
71
+ residualCount: arkOrder.residual?.count,
72
+ },
73
+ });
74
+ if (rulesUnderContract?.mergePlanes) rulesUnderContract.mergePlanes = mergePlanes;
75
+ arkRun.mergePlanes = mergePlanes;
76
+ arkRun.failMergeWhen = mergePlanes.failMergeWhen;
77
+ arkOrder.mergePlanes = mergePlanes;
78
+ arkOrder.failMergeWhen = mergePlanes.failMergeWhen;
79
+ return { arkRun, arkOrder, mergePlanes };
80
+ }
81
+
82
+ export function printCompactExtraDoctorLines(advisories, io) {
83
+ const arkRun = advisories?.arkRun;
84
+ if (arkRun?.active === true && arkRun.notAScore === true) {
85
+ console.log('');
86
+ const residual = Number(arkRun.residual?.count) || 0;
87
+ io.line(residual > 0 ? io.warn : ' ', `ArkRun: ${arkRun.mode || 'on'} · residual=${residual} · not a score`);
88
+ }
89
+ const arkOrder = advisories?.arkOrder;
90
+ if (arkOrder?.active === true && arkOrder.notAScore === true) {
91
+ console.log('');
92
+ const residual = Number(arkOrder.residual?.count) || 0;
93
+ const keys =
94
+ Array.isArray(arkOrder.xiKeys) && arkOrder.xiKeys.length > 0 ? arkOrder.xiKeys.join(', ') : 'unnamed';
95
+ const mark = residual > 0 ? io.warn : ' ';
96
+ io.line(mark, ARKORDER_ONE_BREATH);
97
+ io.line(mark, `ArkOrder: ${arkOrder.mode || 'on'} · xiKeys=${keys} · residual=${residual} · not a score`);
98
+ }
99
+ }
24
100
 
25
101
  function classificationFromCoverage(cov) {
26
102
  return {
@@ -55,25 +131,12 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
55
131
  : undefined);
56
132
  const classification = classificationFromCoverage(cov);
57
133
  const rulesUnderContract = summarizeRulesUnderContract(root, config, factPaths, classification);
58
- const arkRun = summarizeArkRunSection({
59
- arkRun: config?.arkRun,
60
- findings: activeViolations,
134
+ const { arkRun, arkOrder } = attachExtraDoctorSections(
135
+ rulesUnderContract,
136
+ config,
61
137
  classification,
62
- arkRules: {
63
- active: rulesUnderContract?.active === true,
64
- structureEnforced: rulesUnderContract?.mergePlanes?.structureSensors?.enforced,
65
- structureTotal: rulesUnderContract?.mergePlanes?.structureSensors?.total,
66
- structureAdvisory: rulesUnderContract?.mergePlanes?.structureSensors?.advisory,
67
- invariantEnforced: rulesUnderContract?.mergePlanes?.invariants?.enforced,
68
- invariantTotal: rulesUnderContract?.mergePlanes?.invariants?.total,
69
- invariantAdvisory: rulesUnderContract?.mergePlanes?.invariants?.advisory,
70
- covered: rulesUnderContract?.mergePlanes?.invariants?.covered,
71
- uncovered: rulesUnderContract?.mergePlanes?.invariants?.uncovered,
72
- },
73
- });
74
- if (rulesUnderContract?.mergePlanes) {
75
- rulesUnderContract.mergePlanes = arkRun.mergePlanes;
76
- }
138
+ activeViolations
139
+ );
77
140
  return {
78
141
  contractHealth: computeContractHealth(root, config, cov, rules),
79
142
  ambientState: computeAmbientState(ts, root, config, files),
@@ -86,6 +149,7 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
86
149
  stewardNudge: collectStewardNudge(root, config),
87
150
  rulesUnderContract,
88
151
  arkRun,
152
+ arkOrder,
89
153
  };
90
154
  }
91
155
 
@@ -116,4 +180,13 @@ export function printDoctorAdvisories(advisories, io) {
116
180
  io.line(mark, text);
117
181
  }
118
182
  }
183
+ const arkOrder = advisories.arkOrder;
184
+ if (arkOrder && arkOrder.notAScore === true) {
185
+ console.log('');
186
+ console.log(io.color.bold('ArkOrder (not a score)'));
187
+ const mark = arkOrder.active && arkOrder.residual?.count > 0 ? io.warn : ' ';
188
+ for (const text of formatArkOrderDoctorLines(arkOrder)) {
189
+ io.line(mark, text);
190
+ }
191
+ }
119
192
  }
@@ -7,7 +7,7 @@ import { arkCommand } from '../ark-shared.mjs';
7
7
  import { operatingModeTitle } from './product-copy.mjs';
8
8
  import { isDoctorHealthyNothingToDo } from './post-green-path.mjs';
9
9
  import { printParseHealthSection } from './parse-health.mjs';
10
- import { printDoctorAdvisories } from './doctor-advisories.mjs';
10
+ import { printDoctorAdvisories, printCompactExtraDoctorLines } from './doctor-advisories.mjs';
11
11
  import { designDeltaDoctorLines } from './design-delta.mjs';
12
12
  import { enforcementDoctorLines } from './enforcement-state.mjs';
13
13
  import { analysisIncompleteStatement } from './analysis-completeness.mjs';
@@ -164,15 +164,7 @@ export function printDoctorCompactHuman(view) {
164
164
  line(warn, nudge.ask);
165
165
  }
166
166
 
167
- const arkRun = doctorAdvisories.arkRun;
168
- if (arkRun?.active === true && arkRun.notAScore === true) {
169
- console.log('');
170
- const residual = Number(arkRun.residual?.count) || 0;
171
- line(
172
- residual > 0 ? warn : ' ',
173
- `ArkRun: ${arkRun.mode || 'on'} · residual=${residual} · not a score`
174
- );
175
- }
167
+ printCompactExtraDoctorLines(doctorAdvisories, { line, warn });
176
168
 
177
169
  if (violations.length === 0) {
178
170
  if (!analysisComplete) {
@@ -658,11 +658,11 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
658
658
  pilotTarget: residualPilot?.pilotTarget ?? residualPilot?.pilot ?? null,
659
659
  arkRulesMergeHonesty: rulesUnderContract?.mergePlanes
660
660
  ? {
661
- active: rulesUnderContract.active === true || arkRun?.active === true,
661
+ active: rulesUnderContract.active === true || arkRun?.active === true || doctorAdvisories.arkOrder?.active === true,
662
662
  ...rulesUnderContract.mergePlanes,
663
663
  }
664
- : rulesUnderContract?.active === true || arkRun?.active === true
665
- ? { active: true, extraMergeTeeth: arkRun?.extraMergeTeeth === true }
664
+ : rulesUnderContract?.active === true || arkRun?.active === true || doctorAdvisories.arkOrder?.active === true
665
+ ? { active: true, extraMergeTeeth: arkRun?.extraMergeTeeth === true || doctorAdvisories.arkOrder?.extraMergeTeeth === true }
666
666
  : null,
667
667
  primaryNextAction:
668
668
  adopted === 'not-adopted' ? NOT_ADOPTED_NEXT_ACTION : postGreenPath?.action ?? dualTruthNext,
@@ -757,6 +757,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
757
757
  ...doctorAdvisories,
758
758
  rulesUnderContract,
759
759
  arkRun,
760
+ arkOrder: doctorAdvisories.arkOrder,
760
761
  // P0-B — single anti-false-green honesty surface (never a score).
761
762
  productHonesty,
762
763
  governed: cov.governed,
@@ -80,7 +80,7 @@ export function demoteExtraPlaneTeethUnderClassificationFloor(violations, classi
80
80
  return violations;
81
81
  }
82
82
  /** Stamp for extra-plane honesty: never one architecture score. */
83
- export const MERGE_PLANES_DUAL_STAMP = 'Structure = heuristics; invariants = catalog+coverage evidence (not business runtime); ArkRun = kernel usage + declarations (not a score); ArkOrder = pattern slaving (not a score). Extra planes never merge into one architecture score. Advisory ArkRules ≠ merge teeth. Advisory ArkRun ≠ merge teeth. Advisory ArkOrder ≠ merge teeth.';
83
+ export const MERGE_PLANES_DUAL_STAMP = 'Structure = heuristics; invariants = catalog+coverage evidence (not business runtime); ArkRun = kernel usage + declarations (not a score); ArkOrder = the few big product choices (not a score). Extra planes never merge into one architecture score. Advisory ArkRules ≠ merge teeth. Advisory ArkRun ≠ merge teeth. Advisory ArkOrder ≠ merge teeth.';
84
84
  function countOrZero(value) {
85
85
  return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
86
86
  }
@@ -112,7 +112,14 @@ export function composeMergePlanesHonesty(input = {}) {
112
112
  const arkRunResidual = countOrZero(input.arkRun?.residualCount);
113
113
  const arkRunHasEnforced = arkRunPresent && arkRunMode === 'enforced';
114
114
  const arkRunTeeth = arkRunHasEnforced && classificationAllowsTeeth;
115
- const hasEnforcedTeeth = arkRulesHasEnforced || arkRunHasEnforced;
115
+ const arkOrderPresent = input.arkOrder?.present === true;
116
+ const arkOrderMode = input.arkOrder?.mode === 'enforced' || input.arkOrder?.mode === 'advisory'
117
+ ? input.arkOrder.mode
118
+ : null;
119
+ const arkOrderResidual = countOrZero(input.arkOrder?.residualCount);
120
+ const arkOrderHasEnforced = arkOrderPresent && arkOrderMode === 'enforced';
121
+ const arkOrderTeeth = arkOrderHasEnforced && classificationAllowsTeeth;
122
+ const hasEnforcedTeeth = arkRulesHasEnforced || arkRunHasEnforced || arkOrderHasEnforced;
116
123
  const extraMergeTeeth = hasEnforcedTeeth && classificationAllowsTeeth;
117
124
  const teethDeferredForClassification = hasEnforcedTeeth && classificationKnown && !classificationAllowsTeeth;
118
125
  let failMergeWhen;
@@ -122,12 +129,15 @@ export function composeMergePlanesHonesty(input = {}) {
122
129
  extras.push('enforced structure/invariant findings');
123
130
  if (arkRunHasEnforced)
124
131
  extras.push('enforced ArkRun skip findings');
132
+ if (arkOrderHasEnforced)
133
+ extras.push('enforced ArkOrder skip findings');
125
134
  failMergeWhen = `Layer graph failures plus ${extras.join(' and ')} (advisory extras never fail merge alone).`;
126
135
  }
127
136
  else if (teethDeferredForClassification) {
128
137
  const which = [
129
138
  arkRulesHasEnforced ? 'ArkRules structure/invariant' : null,
130
139
  arkRunHasEnforced ? 'ArkRun' : null,
140
+ arkOrderHasEnforced ? 'ArkOrder' : null,
131
141
  ]
132
142
  .filter((part) => Boolean(part))
133
143
  .join(' and ');
@@ -139,15 +149,21 @@ export function composeMergePlanesHonesty(input = {}) {
139
149
  : arkRunMode === 'advisory'
140
150
  ? ' Advisory ArkRun never merge-blocks.'
141
151
  : ' ArkRun extra is present but does not arm merge teeth.';
152
+ const arkOrderBit = !arkOrderPresent
153
+ ? ' Absence of arkOrder is silent.'
154
+ : arkOrderMode === 'advisory'
155
+ ? ' Advisory ArkOrder never merge-blocks.'
156
+ : ' ArkOrder extra is present but does not arm merge teeth.';
142
157
  failMergeWhen =
143
158
  'Layer graph only — no enforced ArkRules structure/invariant teeth on this tree. Advisory packs do not arm merge teeth.' +
144
- arkRunBit;
159
+ arkRunBit +
160
+ arkOrderBit;
145
161
  }
146
162
  const out = {
147
163
  layers: {
148
164
  role: 'inter-layer-edges',
149
165
  alwaysOnGate: true,
150
- note: 'Import/export layer graph — the default merge plane. Absent arkRules or arkRun changes nothing here.',
166
+ note: 'Import/export layer graph — the default merge plane. Absent extras change nothing here.',
151
167
  },
152
168
  structureSensors: {
153
169
  role: 'intra-layer-heuristics',
@@ -177,6 +193,18 @@ export function composeMergePlanesHonesty(input = {}) {
177
193
  : 'Advisory ArkRun never adds merge teeth and never flips valid. Residual is a count, never a score.'
178
194
  : 'Absence of arkRun is silent — Layers and ArkRules verdicts unchanged. The extra never becomes a score.',
179
195
  },
196
+ arkOrder: {
197
+ role: 'pattern-slaving',
198
+ present: arkOrderPresent,
199
+ mode: arkOrderMode,
200
+ residualCount: arkOrderResidual,
201
+ extraMergeTeeth: arkOrderTeeth,
202
+ note: arkOrderPresent
203
+ ? arkOrderMode === 'enforced'
204
+ ? 'Enforced ArkOrder arms extra merge teeth only when the layer plane is classified. Residual is a count, never a score.'
205
+ : 'Advisory ArkOrder never adds merge teeth and never flips valid. Residual is a count, never a score.'
206
+ : 'Absence of arkOrder is silent — Layers verdicts unchanged. The extra never becomes a score.',
207
+ },
180
208
  extraMergeTeeth,
181
209
  dualPlaneStamp: MERGE_PLANES_DUAL_STAMP,
182
210
  failMergeWhen,
@@ -11,6 +11,7 @@ import { effectiveCapabilityDeny } from './analysis-engine.mjs';
11
11
  import { graphBlindSpotsHtml } from './graph-blind.mjs';
12
12
  import { formatRulesUnderContractHtml } from './rules-under-contract.mjs';
13
13
  import { formatArkRunHtml } from './ark-run-report.mjs';
14
+ import { formatArkOrderHtml } from './ark-order-report.mjs';
14
15
  import { primaryImprovementCompassNextAction } from './improvement-compass.mjs';
15
16
 
16
17
  // htmlEscape is injected by the caller (html-report.mjs) — importing it back
@@ -353,6 +354,7 @@ export function renderAdvisorySections(advisories, escape) {
353
354
  graphBlindSpotsHtml(advisories.graphBlindSpots, esc),
354
355
  rulesUnderContractHtml(advisories.rulesUnderContract),
355
356
  formatArkRunHtml(advisories.arkRun, esc),
357
+ formatArkOrderHtml(advisories.arkOrder, esc),
356
358
  ]
357
359
  .filter(Boolean)
358
360
  .join('\n');
@@ -19,7 +19,7 @@ import {
19
19
  buildProductHonesty,
20
20
  } from './enforcement-honesty.mjs';
21
21
  import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
22
- import { summarizeArkRunSection } from './ark-run-doctor.mjs';
22
+ import { attachExtraDoctorSections } from './doctor-advisories.mjs';
23
23
  import { readBaseline, baselineOccurrenceKeys } from './violations.mjs';
24
24
  import { describePackageVersionDualTruth } from './field-install.mjs';
25
25
  import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
@@ -136,23 +136,12 @@ export function buildReportDepthPayload(
136
136
  classifiedFiles: coverage?.governed?.classifiedFiles ?? null,
137
137
  };
138
138
  const rulesUnderContract = summarizeRulesUnderContract(root, config, undefined, classification);
139
- const arkRun = summarizeArkRunSection({
140
- arkRun: config?.arkRun,
141
- findings: activeViolations,
139
+ const { arkRun, arkOrder } = attachExtraDoctorSections(
140
+ rulesUnderContract,
141
+ config,
142
142
  classification,
143
- arkRules: {
144
- active: rulesUnderContract?.active === true,
145
- structureEnforced: rulesUnderContract?.mergePlanes?.structureSensors?.enforced,
146
- structureTotal: rulesUnderContract?.mergePlanes?.structureSensors?.total,
147
- structureAdvisory: rulesUnderContract?.mergePlanes?.structureSensors?.advisory,
148
- invariantEnforced: rulesUnderContract?.mergePlanes?.invariants?.enforced,
149
- invariantTotal: rulesUnderContract?.mergePlanes?.invariants?.total,
150
- invariantAdvisory: rulesUnderContract?.mergePlanes?.invariants?.advisory,
151
- covered: rulesUnderContract?.mergePlanes?.invariants?.covered,
152
- uncovered: rulesUnderContract?.mergePlanes?.invariants?.uncovered,
153
- },
154
- });
155
- if (rulesUnderContract?.mergePlanes) rulesUnderContract.mergePlanes = arkRun.mergePlanes;
143
+ activeViolations
144
+ );
156
145
  // Single residual expression (parity with doctor): nextPilot || extractionCard.
157
146
  const residualPilot =
158
147
  pilotLoop?.nextPilot || pilotLoop?.extractionCard || null;
@@ -174,7 +163,7 @@ export function buildReportDepthPayload(
174
163
  residualPilots: Boolean(residualPilot) && designFitness.designWeak === true,
175
164
  pilotTarget: residualPilot?.pilotTarget ?? residualPilot?.pilot ?? null,
176
165
  arkRulesMergeHonesty: rulesUnderContract?.mergePlanes
177
- ? { active: rulesUnderContract.active === true || arkRun?.active === true, ...rulesUnderContract.mergePlanes }
166
+ ? { active: rulesUnderContract.active === true || arkRun?.active === true || arkOrder?.active === true, ...rulesUnderContract.mergePlanes }
178
167
  : null,
179
168
  primaryNextAction: postGreenPath?.action ?? dualTruthNext,
180
169
  activeBlockingViolations: activeBlockingCount,
@@ -218,6 +207,7 @@ export function buildReportDepthPayload(
218
207
  productHonesty,
219
208
  mergePlanes: rulesUnderContract?.mergePlanes ?? null,
220
209
  arkRun,
210
+ arkOrder,
221
211
  improvementCompass,
222
212
  deepModuleCoach,
223
213
  },
@@ -152,6 +152,8 @@ export function buildReportSnapshot({
152
152
  improvementCompass = null,
153
153
  /** RN08 — thin status ArkRun slice (notAScore; residual count, never a score). */
154
154
  arkRun = null,
155
+ /** Thin status ArkOrder slice (notAScore; residual count, never a score). */
156
+ arkOrder = null,
155
157
  }) {
156
158
  const layers = Array.isArray(config?.layers) ? config.layers : [];
157
159
  const rules = Array.isArray(config?.rules) ? config.rules : [];
@@ -213,6 +215,20 @@ export function buildReportSnapshot({
213
215
  : null,
214
216
  };
215
217
  }
218
+ if (arkOrder && typeof arkOrder === 'object' && arkOrder.notAScore === true) {
219
+ snapshot.arkOrder = {
220
+ notAScore: true,
221
+ present: arkOrder.active === true || arkOrder.present === true,
222
+ mode: arkOrder.mode === 'enforced' || arkOrder.mode === 'advisory' ? arkOrder.mode : null,
223
+ extraMergeTeeth: arkOrder.extraMergeTeeth === true,
224
+ residual:
225
+ typeof arkOrder.residual === 'number'
226
+ ? arkOrder.residual
227
+ : typeof arkOrder.residual?.count === 'number'
228
+ ? arkOrder.residual.count
229
+ : null,
230
+ };
231
+ }
216
232
  return snapshot;
217
233
  }
218
234