arkgate 4.1.0 → 4.2.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 +124 -2
- package/README.md +24 -14
- package/bin/ark-check-runtime.mjs +16 -5
- package/bin/ark-mcp-runtime.mjs +766 -64
- package/bin/lib/agent-gates.mjs +1 -0
- package/bin/lib/ark-gitignore.mjs +88 -0
- package/bin/lib/ci-and-commands.mjs +33 -8
- package/bin/lib/codex-home.mjs +90 -8
- package/bin/lib/design-smells.mjs +71 -9
- package/bin/lib/doctor-plan.mjs +47 -39
- package/bin/lib/effective-contract-load.mjs +73 -9
- package/bin/lib/enforcement-honesty.mjs +78 -22
- package/bin/lib/enforcement-state.mjs +1 -1
- package/bin/lib/gate-files.mjs +441 -9
- package/bin/lib/github-enforcement.mjs +168 -7
- package/bin/lib/hook-templates.mjs +12 -11
- package/bin/lib/host-support-matrix.mjs +91 -17
- package/bin/lib/html-report-depth.mjs +13 -2
- package/bin/lib/html-report-evolution.mjs +114 -0
- package/bin/lib/html-report.mjs +18 -97
- package/bin/lib/import-resolve.mjs +33 -11
- package/bin/lib/install-activation.mjs +87 -0
- package/bin/lib/install-migrate.mjs +66 -50
- package/bin/lib/managed-upgrade.mjs +10 -41
- package/bin/lib/mcp-adoption.mjs +15 -5
- package/bin/lib/pilot-loop.mjs +25 -8
- package/bin/lib/project-identity.mjs +103 -0
- package/bin/lib/report-snapshot-context.mjs +28 -0
- package/bin/lib/resident-hook.mjs +33 -9
- package/bin/lib/rules-inventory.mjs +100 -8
- package/bin/lib/skill-install.mjs +272 -22
- package/bin/lib/skill-write.mjs +899 -0
- package/bin/lib/start-preview.mjs +84 -1
- package/bin/lib/upgrade-command.mjs +2 -5
- package/bin/lib/write-path-detect.mjs +2 -2
- package/dist/index.cjs +13 -13
- package/dist/index.d.ts +194 -2
- package/dist/index.js +13 -13
- package/docs/README.md +6 -4
- package/docs/agent-guide.md +115 -17
- package/docs/ai-gates.md +133 -25
- package/docs/assets/ark-write-gate.svg +2 -2
- package/docs/develop.md +16 -6
- package/docs/enthusiast/how-to-agent-gates.md +6 -0
- package/docs/package-surface.md +16 -9
- package/docs/product-voice.md +22 -4
- package/docs/use.md +3 -1
- package/package.json +3 -1
- package/schemas/ark.project-identity.schema.json +116 -0
- package/server.json +2 -2
- package/templates/skills/ark-adopt.md +9 -0
- package/templates/skills/ark-architect.md +12 -2
- package/templates/skills/ark-autopilot.md +9 -0
- package/templates/skills/ark-contract.md +11 -1
- package/templates/skills/ark-coverage.md +9 -0
- package/templates/skills/ark-explain.md +13 -1
- package/templates/skills/ark-explore.md +9 -0
- package/templates/skills/ark-fix.md +10 -1
- package/templates/skills/ark-loop.md +11 -2
- package/templates/skills/ark-place.md +17 -6
- package/templates/skills/ark-runtime.md +8 -0
- package/templates/skills/ark-think.md +14 -2
- package/templates/skills/ark-upgrade.md +9 -0
|
@@ -10,6 +10,35 @@ import {
|
|
|
10
10
|
loadArkRulesContract,
|
|
11
11
|
} from './arkrules-contract.mjs';
|
|
12
12
|
|
|
13
|
+
function normalizeProjectRelativePath(value) {
|
|
14
|
+
const normalized = value.replace(/\\/g, '/');
|
|
15
|
+
if (
|
|
16
|
+
!normalized ||
|
|
17
|
+
normalized.startsWith('/') ||
|
|
18
|
+
/^[A-Za-z]:/.test(normalized) ||
|
|
19
|
+
normalized.includes('\0')
|
|
20
|
+
) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const segments = [];
|
|
24
|
+
for (const segment of normalized.split('/')) {
|
|
25
|
+
if (!segment || segment === '.') continue;
|
|
26
|
+
if (segment === '..') return undefined;
|
|
27
|
+
segments.push(segment);
|
|
28
|
+
}
|
|
29
|
+
return segments.length > 0 ? segments.join('/') : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isWithinRoot(root, candidate) {
|
|
33
|
+
const relative = path.relative(root, candidate);
|
|
34
|
+
return (
|
|
35
|
+
relative === '' ||
|
|
36
|
+
(!relative.startsWith(`..${path.sep}`) &&
|
|
37
|
+
relative !== '..' &&
|
|
38
|
+
!path.isAbsolute(relative))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
13
42
|
/**
|
|
14
43
|
* @param {string} root
|
|
15
44
|
* @param {Record<string, unknown>} config loaded ark.config.json object
|
|
@@ -29,6 +58,7 @@ export function loadEffectiveArkRulesFromDisk(root, config, opts = {}) {
|
|
|
29
58
|
const warnings = [];
|
|
30
59
|
const parts = [];
|
|
31
60
|
const referenced = new Set();
|
|
61
|
+
const canonicalRoot = fs.realpathSync(root);
|
|
32
62
|
|
|
33
63
|
for (const layer of Object.keys(refs).sort()) {
|
|
34
64
|
const relRaw = refs[layer];
|
|
@@ -37,10 +67,12 @@ export function loadEffectiveArkRulesFromDisk(root, config, opts = {}) {
|
|
|
37
67
|
errors.push({ path: pathKey, message: 'must be a non-empty relative path string' });
|
|
38
68
|
continue;
|
|
39
69
|
}
|
|
40
|
-
|
|
70
|
+
const rel = normalizeProjectRelativePath(relRaw);
|
|
71
|
+
if (!rel) {
|
|
41
72
|
errors.push({
|
|
42
73
|
path: pathKey,
|
|
43
|
-
message:
|
|
74
|
+
message:
|
|
75
|
+
'must be a project-relative path without absolute roots or parent-directory traversal',
|
|
44
76
|
});
|
|
45
77
|
continue;
|
|
46
78
|
}
|
|
@@ -52,17 +84,42 @@ export function loadEffectiveArkRulesFromDisk(root, config, opts = {}) {
|
|
|
52
84
|
continue;
|
|
53
85
|
}
|
|
54
86
|
|
|
55
|
-
const rel = relRaw.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
56
87
|
referenced.add(rel);
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
const lexicalTarget = path.resolve(canonicalRoot, ...rel.split('/'));
|
|
89
|
+
if (!isWithinRoot(canonicalRoot, lexicalTarget)) {
|
|
90
|
+
errors.push({
|
|
91
|
+
path: pathKey,
|
|
92
|
+
message: `referenced ArkRules path ${JSON.stringify(rel)} resolves outside the project root`,
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!fs.existsSync(lexicalTarget)) {
|
|
60
97
|
errors.push({
|
|
61
98
|
path: pathKey,
|
|
62
99
|
message: `referenced ArkRules file ${JSON.stringify(rel)} is missing`,
|
|
63
100
|
});
|
|
64
101
|
continue;
|
|
65
102
|
}
|
|
103
|
+
let absolute;
|
|
104
|
+
try {
|
|
105
|
+
absolute = fs.realpathSync(lexicalTarget);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
errors.push({
|
|
108
|
+
path: pathKey,
|
|
109
|
+
message: `referenced ArkRules file ${JSON.stringify(rel)} could not be resolved: ${
|
|
110
|
+
error instanceof Error ? error.message : String(error)
|
|
111
|
+
}`,
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!isWithinRoot(canonicalRoot, absolute)) {
|
|
116
|
+
errors.push({
|
|
117
|
+
path: pathKey,
|
|
118
|
+
message: `referenced ArkRules path ${JSON.stringify(rel)} resolves outside the project root`,
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
opts.observeInput?.(absolute, 'arkrules');
|
|
66
123
|
let content;
|
|
67
124
|
try {
|
|
68
125
|
content = fs.readFileSync(absolute, 'utf8');
|
|
@@ -90,9 +147,16 @@ export function loadEffectiveArkRulesFromDisk(root, config, opts = {}) {
|
|
|
90
147
|
}
|
|
91
148
|
|
|
92
149
|
// Drift: unreferenced files under arkrules/
|
|
93
|
-
const arkrulesDir = path.join(
|
|
94
|
-
|
|
95
|
-
|
|
150
|
+
const arkrulesDir = path.join(canonicalRoot, 'arkrules');
|
|
151
|
+
const resolvedArkRulesDir = fs.existsSync(arkrulesDir)
|
|
152
|
+
? fs.realpathSync(arkrulesDir)
|
|
153
|
+
: undefined;
|
|
154
|
+
if (
|
|
155
|
+
resolvedArkRulesDir &&
|
|
156
|
+
isWithinRoot(canonicalRoot, resolvedArkRulesDir) &&
|
|
157
|
+
fs.statSync(resolvedArkRulesDir).isDirectory()
|
|
158
|
+
) {
|
|
159
|
+
for (const name of fs.readdirSync(resolvedArkRulesDir).sort()) {
|
|
96
160
|
if (!name.endsWith('.json')) continue;
|
|
97
161
|
const rel = `arkrules/${name}`;
|
|
98
162
|
if (!referenced.has(rel)) {
|
|
@@ -207,7 +207,8 @@ export function buildWritePathHonesty(activeHost, hardWriteActive = false, extra
|
|
|
207
207
|
hardWriteSupported: hardCapable,
|
|
208
208
|
hardWriteActive: effectiveHard,
|
|
209
209
|
hardWriteUnverified: hardCapable && !effectiveHard,
|
|
210
|
-
hardMergeBoundary:
|
|
210
|
+
hardMergeBoundary:
|
|
211
|
+
'required-github-status-context (CLI: arkgate-check --strict-merge / ark-check --strict-merge)',
|
|
211
212
|
packageInstalled,
|
|
212
213
|
packagePinAbsent: pinAbsentForUser,
|
|
213
214
|
...(pinCode ? { packagePinCode: pinCode } : {}),
|
|
@@ -367,10 +368,15 @@ export function buildProductHonesty(input = {}) {
|
|
|
367
368
|
});
|
|
368
369
|
}
|
|
369
370
|
|
|
371
|
+
// EH05: soft-write-host is a permanent host posture residual — keep in evidence,
|
|
372
|
+
// do NOT alone force architecture "Not finished". Reclassified out of contract debt.
|
|
370
373
|
if (write?.softWriteHost) {
|
|
371
374
|
reasons.push({
|
|
372
375
|
id: 'soft-write-host',
|
|
373
|
-
|
|
376
|
+
bucket: 'environment',
|
|
377
|
+
message:
|
|
378
|
+
write.message ||
|
|
379
|
+
'Local write is advisory; hard merge boundary = a required GitHub status context running arkgate-check --strict-merge (alias ark-check --strict-merge).',
|
|
374
380
|
});
|
|
375
381
|
}
|
|
376
382
|
|
|
@@ -399,7 +405,13 @@ export function buildProductHonesty(input = {}) {
|
|
|
399
405
|
// Informational only when no enforced arkrule plane — does not alone make unfinished.
|
|
400
406
|
}
|
|
401
407
|
|
|
402
|
-
|
|
408
|
+
// EH05: environment residual deny-list (future reason ids stay architecture debt by default).
|
|
409
|
+
const ENVIRONMENT_REASON_IDS = new Set(['soft-write-host']);
|
|
410
|
+
|
|
411
|
+
const environmentResiduals = reasons.filter((r) => ENVIRONMENT_REASON_IDS.has(r.id));
|
|
412
|
+
const architectureReasons = reasons.filter((r) => !ENVIRONMENT_REASON_IDS.has(r.id));
|
|
413
|
+
// unfinished = any non-environment residual (deny-list env, not allowlist architecture)
|
|
414
|
+
const unfinished = architectureReasons.length > 0;
|
|
403
415
|
const wholeTreeGoverned = wholeTreeGovernedEarly;
|
|
404
416
|
const coverageIncomplete =
|
|
405
417
|
cov?.status === 'empty-scope' ||
|
|
@@ -407,31 +419,56 @@ export function buildProductHonesty(input = {}) {
|
|
|
407
419
|
cov?.greenIsNotEnforcement === true ||
|
|
408
420
|
!wholeTreeGoverned;
|
|
409
421
|
|
|
422
|
+
const softWriteOnly =
|
|
423
|
+
!unfinished && environmentResiduals.some((r) => r.id === 'soft-write-host');
|
|
424
|
+
const hostLabel = (() => {
|
|
425
|
+
const h = typeof write?.activeHost === 'string' ? write.activeHost.trim().toLowerCase() : '';
|
|
426
|
+
if (h === 'codex') return 'Codex';
|
|
427
|
+
if (h === 'cursor') return 'Cursor';
|
|
428
|
+
if (h === 'opencode') return 'OpenCode';
|
|
429
|
+
if (h) return h;
|
|
430
|
+
return 'this host';
|
|
431
|
+
})();
|
|
432
|
+
|
|
410
433
|
const primary =
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
434
|
+
architectureReasons.find((r) => r.id === 'active-blocking-violations') ||
|
|
435
|
+
architectureReasons.find((r) => r.id === 'mode-adapt-with-debt') ||
|
|
436
|
+
architectureReasons.find((r) => r.id === 'mode-suggest-with-debt') ||
|
|
437
|
+
architectureReasons.find((r) => r.id === 'design-weak') ||
|
|
438
|
+
architectureReasons.find((r) => r.id === 'design-smells-open-edges') ||
|
|
439
|
+
architectureReasons.find((r) => r.id === 'coverage-weak-or-empty') ||
|
|
440
|
+
architectureReasons.find((r) => r.id === 'dirty-freeze') ||
|
|
441
|
+
architectureReasons.find((r) => r.id === 'package-version-dual-truth') ||
|
|
442
|
+
architectureReasons.find((r) => r.id === 'package-pin-absent') ||
|
|
443
|
+
architectureReasons.find((r) => r.id === 'baseline-missing-with-debt') ||
|
|
444
|
+
architectureReasons.find((r) => r.id === 'residual-pilot') ||
|
|
445
|
+
architectureReasons[0] ||
|
|
446
|
+
environmentResiduals[0];
|
|
423
447
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
448
|
+
let primaryMessage;
|
|
449
|
+
if (unfinished) {
|
|
450
|
+
primaryMessage =
|
|
451
|
+
primary?.message ||
|
|
452
|
+
'Not finished: residual honesty signals remain (violations, mode, coverage, freeze, design, package pin, or pilots).';
|
|
453
|
+
} else if (softWriteOnly) {
|
|
454
|
+
primaryMessage = `${hostLabel} local writes stay advisory/bypassable; architecture contract on this slice is ready. Hard merge boundary is a required GitHub status context running arkgate-check --strict-merge (alias ark-check --strict-merge).`;
|
|
455
|
+
} else if (wholeTreeGoverned) {
|
|
456
|
+
primaryMessage =
|
|
457
|
+
'No residual honesty blockers on this slice — still not a numeric architecture score; re-doctor after material change.';
|
|
458
|
+
} else {
|
|
459
|
+
primaryMessage =
|
|
460
|
+
'No residual honesty blockers flagged — green is only as wide as the governed slice.';
|
|
461
|
+
}
|
|
430
462
|
|
|
431
463
|
// P0B-HEADLINE: dual-truth / pin-only unfinished must not claim "not whole-tree"
|
|
432
464
|
// when the governed tree is already 100%.
|
|
465
|
+
// EH05: soft-write alone → composite readiness headline, never global "Not finished".
|
|
433
466
|
let headline;
|
|
434
|
-
if (!unfinished) {
|
|
467
|
+
if (!unfinished && softWriteOnly) {
|
|
468
|
+
headline = wholeTreeGoverned
|
|
469
|
+
? `Architecture contract ready; ${hostLabel} local writes are advisory`
|
|
470
|
+
: `Contract residual clear; ${hostLabel} local writes are advisory`;
|
|
471
|
+
} else if (!unfinished) {
|
|
435
472
|
headline = 'Honesty clear on residual signals';
|
|
436
473
|
} else if (coverageIncomplete) {
|
|
437
474
|
headline = 'Not finished / not whole-tree guarantee';
|
|
@@ -440,6 +477,7 @@ export function buildProductHonesty(input = {}) {
|
|
|
440
477
|
}
|
|
441
478
|
|
|
442
479
|
// Prefer caller next action; dual-truth / pin-absent get install/pin path when empty.
|
|
480
|
+
// Soft-write-only must not leave a failure headline with null next action (EH05).
|
|
443
481
|
let primaryNextAction = input.primaryNextAction || null;
|
|
444
482
|
if (!primaryNextAction && dualTruth) {
|
|
445
483
|
const ver = input.packageVersionTruth?.cliVersion;
|
|
@@ -449,8 +487,20 @@ export function buildProductHonesty(input = {}) {
|
|
|
449
487
|
} else if (!primaryNextAction && pinAbsent) {
|
|
450
488
|
primaryNextAction =
|
|
451
489
|
'Add arkgate to package.json and install so CI/npx resolve this CLI (PACKAGE_PIN_ABSENT)';
|
|
490
|
+
} else if (!primaryNextAction && softWriteOnly) {
|
|
491
|
+
primaryNextAction =
|
|
492
|
+
'Confirm the GitHub required status context name runs arkgate-check --strict-merge (or ark-check --strict-merge). Soft-write hosts stay advisory at local write; the required status is the hard merge boundary.';
|
|
452
493
|
}
|
|
453
494
|
|
|
495
|
+
const contractReadiness = unfinished ? 'not-ready' : wholeTreeGoverned ? 'ready' : 'partial';
|
|
496
|
+
const localWriteBoundary = write?.softWriteHost
|
|
497
|
+
? 'advisory'
|
|
498
|
+
: write?.hardWriteActive
|
|
499
|
+
? 'hard'
|
|
500
|
+
: write?.hardWriteSupported
|
|
501
|
+
? 'unverified'
|
|
502
|
+
: 'unknown';
|
|
503
|
+
|
|
454
504
|
return {
|
|
455
505
|
finished: !unfinished && wholeTreeGoverned && !designWeak && activeBlocking === 0,
|
|
456
506
|
elegant: !designWeak && !base?.dirtyBaselineRisk && !designSmellsOpenEdges && activeBlocking === 0,
|
|
@@ -462,8 +512,14 @@ export function buildProductHonesty(input = {}) {
|
|
|
462
512
|
activeBlocking === 0,
|
|
463
513
|
unfinished,
|
|
464
514
|
notAScore: true,
|
|
515
|
+
// Full evidence including soft-write-host (reclassified, not silenced)
|
|
465
516
|
reasonIds: reasons.map((r) => r.id),
|
|
466
517
|
reasons,
|
|
518
|
+
architectureReasonIds: architectureReasons.map((r) => r.id),
|
|
519
|
+
environmentResidualIds: environmentResiduals.map((r) => r.id),
|
|
520
|
+
environmentResiduals,
|
|
521
|
+
contractReadiness,
|
|
522
|
+
localWriteBoundary,
|
|
467
523
|
primaryMessage,
|
|
468
524
|
primaryNextAction,
|
|
469
525
|
headline,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated from enforcement-state.source.mjs — run npm run generate:packaged-tooling.
|
|
2
|
-
import
|
|
2
|
+
import m from"node:fs";import{createRequire as q}from"node:module";import h from"node:path";const n="unverified";function O({configuredOnDisk:a=!1,restartRequired:e=a}={}){return{configuredOnDisk:!!a,restartRequired:!!e,runtimeObserved:!1,identityMatch:n,active:!1}}function M(a){const e=h.join(a,"package.json");try{if(JSON.parse(m.readFileSync(e,"utf8"))?.name==="arkgate"&&m.statSync(h.join(a,"bin","ark-check.mjs"),{throwIfNoEntry:!1})?.isFile())return{installed:!0,source:"package.json + bin/ark-check.mjs (self-host)",selfHost:!0}}catch{}try{const r=q(e).resolve("arkgate/package.json"),o=h.dirname(r),i=JSON.parse(m.readFileSync(r,"utf8")),s=h.join(o,"bin","ark-check.mjs");if(i?.name==="arkgate"&&m.statSync(s,{throwIfNoEntry:!1})?.isFile())return{installed:!0,source:"arkgate/package.json via project resolver",selfHost:!1}}catch{}return{installed:!1,source:"arkgate/package.json unresolved from project",selfHost:!1}}function S(a){return a.length>0?a:["filesystem scan (no matching configuration)"]}function g({supported:a,configuredPaths:e,installed:r,active:o,runtimeObserved:i,operation:s,operationCoverage:u,bypassable:v,required:c,hard:t,sources:l}){const p=e.length>0,d=!!r.installed;return{supported:a,analyzed:!0,configured:p,installed:d,active:o,runtimeObserved:i,operation:s,operationCoverage:u,bypassable:v,required:c,hard:t,evidence:[...S(e).map(f=>({field:"configured",source:f,value:p})),{field:"installed",source:r.source,value:d},{field:"active",source:l.active,value:o},{field:"runtimeObserved",source:l.runtimeObserved,value:i},{field:"operationCoverage",source:l.operationCoverage,value:u},{field:"bypassable",source:l.bypassable,value:v},{field:"required",source:l.required,value:c},{field:"hard",source:l.hard,value:t}]}}function E(a,e){const r=M(a),o=!!e.support?.capabilities?.["hard-write"],i=!!e.support?.capabilities?.["advisory-write"],s=e.capabilityEvidence["hard-write"],u=e.capabilityEvidence["advisory-write"],v=e.capabilityEvidence["merge-gate"],c=e.enforcementLadder.localWrite,t=typeof c.operationCovered=="boolean",l=t?c.operationCovered:n,p=t&&l===!0,d=!!(o&&r.installed&&p&&c.hard===!0),f=t?p&&r.installed:o&&s.length>0&&r.installed?n:!1,b=i&&u.length>0&&r.installed?n:!1,y=!!(e.ci?.failClosed&&v.length>0),C=y&&r.installed?n:!1;return{schemaVersion:"1.1",activeHost:e.activeHost,localWrite:g({supported:o,configuredPaths:s,installed:r,active:f,runtimeObserved:t,operation:t?c.operation??null:null,operationCoverage:l,bypassable:d?!1:o&&!t?n:!0,required:n,hard:d,sources:{active:t?"observed PreToolUse attempt":"runtime observation unavailable",runtimeObserved:t?"fresh PreToolUse invocation":"runtime observation unavailable",operationCoverage:t?"active-host operation matcher":"operation not observed",bypassable:d?"observed hard write boundary":"host runtime bypass evidence unavailable",required:"local host policy unavailable",hard:d?"fresh covered active-host invocation":"hardness not proven for this invocation"}}),advisoryMcp:g({supported:i,configuredPaths:u,installed:r,active:b,runtimeObserved:!1,operation:null,operationCoverage:n,bypassable:!0,required:n,hard:!1,sources:{active:"MCP runtime observation unavailable",runtimeObserved:"doctor did not observe an MCP tool invocation",operationCoverage:"advisory MCP is caller-invoked",bypassable:"advisory MCP does not intercept every write",required:"local host policy unavailable",hard:"MCP presence is advisory and never proves a hard boundary"}}),ciMerge:g({supported:!0,configuredPaths:y?v:[],installed:r,active:C,runtimeObserved:!1,operation:"merge",operationCoverage:y?n:!1,bypassable:y?n:!0,required:n,hard:!1,sources:{active:"CI run and provider enforcement not observed",runtimeObserved:"provider evidence unavailable",operationCoverage:"required-status operation coverage unavailable",bypassable:"branch-protection evidence unavailable",required:"branch-protection evidence unavailable",hard:"merge hardness requires fresh provider evidence"}})}}function $(a,e,r,o){return{...a,...o,evidence:[...a.evidence.filter(i=>!e.includes(i.field)),...e.map(i=>({field:i,source:r,value:o[i]}))]}}function x(a,e){if(!e)return a;const r=e.reason==="provider-policy-unavailable-plan"||e.policyReason==="unavailable-plan",o=e.available===!0,i=!o&&!r&&(e.reason==="provider-enforcement-unverified"||e.reason==="gh-cli-unavailable"||e.reason==="gh-repo-unavailable"||!!e.reason);if(!o&&e.runtimeObserved!==!0&&!r&&!i)return a;const s=o?typeof e.arkCheckRequired=="boolean"?e.arkCheckRequired:n:r?!1:n,u=!!(a.enforcementState.ciMerge.configured&&a.enforcementState.ciMerge.installed),v=s===!0?u:s===!1?!1:u?n:!1,c=v===!0?e.arkCheckSourceBound===!1?!0:n:s===!1?!0:u?n:!0,t=e.runtimeObserved===!0,l=o?`GitHub branch protection (${e.repo??"repository"}:${e.branch??"default"})`:r?`GitHub provider policy unavailable (plan) (${e.repo??"repository"}:${e.branch??"default"})`:`GitHub CI runtime (${e.repo??"repository"})`,p=s,d=v===!0&&c===!1&&p===!0,f=$(a.enforcementState.ciMerge,["active","runtimeObserved","operationCoverage","bypassable","required","hard"],l,{active:v,runtimeObserved:t,operationCoverage:p,bypassable:c,required:s,hard:d}),b={...a,enforcementState:{...a.enforcementState,ciMerge:f},enforcementLadder:{...a.enforcementLadder,ciMerge:{...a.enforcementLadder.ciMerge,requiredStatus:s,...e.latestCiRun?{latestCiRun:e.latestCiRun}:{},...r?{providerPolicy:"unavailable-plan"}:{}}}};return(r||e.reason)&&(b.providerEnforcement={available:o,reason:e.reason||(r?"provider-policy-unavailable-plan":"provider-enforcement-unverified"),policyReason:e.policyReason||(r?"unavailable-plan":null),runtimeObserved:t,latestCiRun:e.latestCiRun??null,hard:d===!0}),b}function k(a,e){const r=o=>o===!0?"yes":o===!1?"no":String(o);return`${a} \u2014 supported: ${r(e.supported)} \xB7 analyzed: ${r(e.analyzed)} \xB7 configured: ${r(e.configured)} \xB7 installed: ${r(e.installed)} \xB7 runtime observed: ${r(e.runtimeObserved)} \xB7 operation: ${e.operation??"none"} \xB7 operation covered: ${r(e.operationCoverage)} \xB7 active: ${r(e.active)} \xB7 bypassable: ${r(e.bypassable)} \xB7 required: ${r(e.required)} \xB7 hard: ${r(e.hard)}`}function B(a){const e=[{level:a.localWrite.active===!0?"ok":"warn",text:k("Local write",a.localWrite)},{level:"warn",text:k("Advisory MCP",a.advisoryMcp)},{level:a.ciMerge.required===!0?"ok":"warn",text:k("CI merge",a.ciMerge)}];return a.localWrite.active===n&&a.localWrite.hard===!1&&e.push({level:"bad",text:"RED FLAG: local hook assets exist, but this active-host operation was not observed at runtime; hard blocking is unverified."}),a.activeHost==="unknown"&&e.push({level:"warn",text:"Active host unknown for this invocation \u2014 enforcementState is session projection only. See writePath.inventory for on-disk host hooks; hard write is never claimed without runtime proof."}),e}export{E as buildEnforcementState,O as codexRuntimeActivation,B as enforcementDoctorLines,M as packageInstallation,x as withCiProviderEvidence};
|