@deftai/directive-core 0.107.0 → 0.109.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.
Files changed (271) hide show
  1. package/dist/architecture/sor-preflight.js +2 -0
  2. package/dist/cache/operations.js +1 -1
  3. package/dist/check/cached-orchestrator.js +28 -3
  4. package/dist/check/gate-lists.d.ts +14 -0
  5. package/dist/check/gate-lists.js +26 -3
  6. package/dist/check/named-cause.js +18 -3
  7. package/dist/check/session-completed-ac.d.ts +1 -1
  8. package/dist/check/session-completed-ac.js +1 -1
  9. package/dist/codebase/provider.js +7 -1
  10. package/dist/consumer-check-contract/evaluate.d.ts +47 -0
  11. package/dist/consumer-check-contract/evaluate.js +181 -15
  12. package/dist/delivery-attempt/evaluate.d.ts +9 -1
  13. package/dist/delivery-attempt/evaluate.js +69 -0
  14. package/dist/delivery-attempt/handoff.js +1 -1
  15. package/dist/delivery-attempt/index.d.ts +1 -1
  16. package/dist/delivery-attempt/index.js +1 -1
  17. package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
  18. package/dist/deposit/live-procedure-exclusions.js +110 -0
  19. package/dist/deposit/live-procedure-targets.d.ts +45 -0
  20. package/dist/deposit/live-procedure-targets.js +274 -0
  21. package/dist/deposit/python-free.d.ts +6 -0
  22. package/dist/deposit/python-free.js +15 -0
  23. package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
  24. package/dist/deposit/rewrite-deposit-links.js +148 -0
  25. package/dist/deposit/run-stage-content-pack.d.ts +2 -0
  26. package/dist/deposit/run-stage-content-pack.js +3 -0
  27. package/dist/deposit/stage-content-pack.d.ts +17 -0
  28. package/dist/deposit/stage-content-pack.js +91 -0
  29. package/dist/design-critique/citation-grammar.d.ts +60 -0
  30. package/dist/design-critique/citation-grammar.js +303 -0
  31. package/dist/design-critique/completed-arc-record.d.ts +62 -0
  32. package/dist/design-critique/completed-arc-record.js +277 -0
  33. package/dist/freshness/bind.d.ts +32 -3
  34. package/dist/freshness/bind.js +58 -12
  35. package/dist/hooks/classify/host-session-identity.d.ts +89 -0
  36. package/dist/hooks/classify/host-session-identity.js +467 -0
  37. package/dist/hooks/classify/index.d.ts +3 -2
  38. package/dist/hooks/classify/index.js +3 -2
  39. package/dist/hooks/classify/paths.d.ts +9 -0
  40. package/dist/hooks/classify/paths.js +39 -0
  41. package/dist/hooks/classify/stdin.d.ts +5 -0
  42. package/dist/hooks/classify/stdin.js +94 -1
  43. package/dist/hooks/dest-form.d.ts +20 -1
  44. package/dist/hooks/dest-form.js +158 -21
  45. package/dist/hooks/dispatcher.d.ts +58 -3
  46. package/dist/hooks/dispatcher.js +762 -73
  47. package/dist/hooks/fixtures/cases.d.ts +22 -2
  48. package/dist/hooks/fixtures/cases.js +228 -0
  49. package/dist/hooks/git-destructive-log.d.ts +32 -0
  50. package/dist/hooks/git-destructive-log.js +46 -0
  51. package/dist/hooks/index.d.ts +2 -0
  52. package/dist/hooks/index.js +2 -0
  53. package/dist/hooks/scope.js +3 -1
  54. package/dist/hooks/shell-write-targets.d.ts +10 -0
  55. package/dist/hooks/shell-write-targets.js +274 -0
  56. package/dist/hooks/tools.d.ts +20 -2
  57. package/dist/hooks/tools.js +30 -1
  58. package/dist/init-deposit/agent-hooks.d.ts +2 -1
  59. package/dist/init-deposit/agent-hooks.js +8 -1
  60. package/dist/init-deposit/gitignore.d.ts +7 -0
  61. package/dist/init-deposit/gitignore.js +24 -0
  62. package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
  63. package/dist/init-deposit/hook-runtime-travel.js +269 -0
  64. package/dist/init-deposit/init-deposit.js +3 -0
  65. package/dist/init-deposit/refresh.js +7 -0
  66. package/dist/init-deposit/runtime-writers.d.ts +14 -0
  67. package/dist/init-deposit/runtime-writers.js +33 -0
  68. package/dist/intake/clause-derivation.js +9 -3
  69. package/dist/intake/issue-ingest.d.ts +6 -1
  70. package/dist/intake/issue-ingest.js +83 -4
  71. package/dist/intake/platform-capabilities.d.ts +9 -2
  72. package/dist/intake/platform-capabilities.js +38 -9
  73. package/dist/intake/reconcile-issues.js +13 -13
  74. package/dist/lifecycle/brief-envelope.d.ts +25 -0
  75. package/dist/lifecycle/brief-envelope.js +42 -0
  76. package/dist/lifecycle/index.d.ts +1 -0
  77. package/dist/lifecycle/index.js +1 -0
  78. package/dist/literal-acceptance/evaluate.js +14 -5
  79. package/dist/literal-acceptance/index.d.ts +1 -1
  80. package/dist/literal-acceptance/index.js +1 -1
  81. package/dist/literal-acceptance/run.d.ts +2 -0
  82. package/dist/literal-acceptance/run.js +19 -1
  83. package/dist/orphan-active/candidate-scope.d.ts +53 -0
  84. package/dist/orphan-active/candidate-scope.js +157 -0
  85. package/dist/orphan-active/evaluate.d.ts +52 -0
  86. package/dist/orphan-active/evaluate.js +250 -128
  87. package/dist/orphan-active/index.d.ts +2 -0
  88. package/dist/orphan-active/index.js +2 -0
  89. package/dist/orphan-active/issue-state.d.ts +121 -0
  90. package/dist/orphan-active/issue-state.js +278 -0
  91. package/dist/platform/cursor-managed-runtime.d.ts +95 -0
  92. package/dist/platform/cursor-managed-runtime.js +241 -0
  93. package/dist/platform/index.d.ts +1 -0
  94. package/dist/platform/index.js +1 -0
  95. package/dist/platform/platform-capabilities.d.ts +23 -1
  96. package/dist/platform/platform-capabilities.js +55 -15
  97. package/dist/policy/ceremony-dial.js +5 -9
  98. package/dist/policy/host-hooks.js +4 -9
  99. package/dist/policy/merge-approval-head.js +7 -6
  100. package/dist/policy/org-force-on-migration.js +5 -10
  101. package/dist/policy/plan-extensions.d.ts +14 -2
  102. package/dist/policy/plan-extensions.js +24 -3
  103. package/dist/policy/product-signal.js +5 -10
  104. package/dist/policy/require-human-merge.js +5 -9
  105. package/dist/policy/resolve.js +77 -16
  106. package/dist/policy/value-feedback.js +9 -18
  107. package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
  108. package/dist/pr-closeout-attestable/evaluate.js +306 -0
  109. package/dist/pr-closeout-attestable/index.d.ts +2 -0
  110. package/dist/pr-closeout-attestable/index.js +2 -0
  111. package/dist/pr-closing-keywords/gh.js +32 -9
  112. package/dist/pr-closing-keywords/main.d.ts +1 -0
  113. package/dist/pr-closing-keywords/main.js +68 -3
  114. package/dist/pr-closing-keywords/types.d.ts +2 -0
  115. package/dist/pr-merge-readiness/gh.js +32 -9
  116. package/dist/pr-protected-issues/gh.d.ts +6 -2
  117. package/dist/pr-protected-issues/gh.js +42 -11
  118. package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
  119. package/dist/pr-wait-mergeable/cascade.js +21 -1
  120. package/dist/pr-wait-mergeable/types.d.ts +2 -0
  121. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  122. package/dist/pr-wait-mergeable/wrappers.js +14 -3
  123. package/dist/preflight/evaluate.d.ts +15 -0
  124. package/dist/preflight/evaluate.js +29 -3
  125. package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
  126. package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
  127. package/dist/product-first-done-gate/evaluate.js +23 -9
  128. package/dist/product-first-done-gate/types.js +2 -0
  129. package/dist/release/cli-drift-report.d.ts +68 -0
  130. package/dist/release/cli-drift-report.js +189 -0
  131. package/dist/release/consumer-hard-stops.d.ts +48 -0
  132. package/dist/release/consumer-hard-stops.js +140 -0
  133. package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
  134. package/dist/release/consumer-readiness-disclosure.js +51 -0
  135. package/dist/release/index.d.ts +3 -0
  136. package/dist/release/index.js +3 -0
  137. package/dist/release/issue-state-fetch.d.ts +5 -3
  138. package/dist/release/issue-state-fetch.js +86 -21
  139. package/dist/release/native-steps.js +1 -0
  140. package/dist/release/pipeline.js +74 -0
  141. package/dist/release/run-consumer-readiness.d.ts +15 -0
  142. package/dist/release/run-consumer-readiness.js +24 -0
  143. package/dist/release/types.d.ts +20 -0
  144. package/dist/render/constants.d.ts +4 -0
  145. package/dist/render/constants.js +11 -8
  146. package/dist/render/export-spec.js +31 -5
  147. package/dist/render/index.d.ts +1 -1
  148. package/dist/render/index.js +1 -1
  149. package/dist/render/prd-render.d.ts +4 -1
  150. package/dist/render/prd-render.js +82 -23
  151. package/dist/render/project-render.js +14 -7
  152. package/dist/render/scope-outlook.d.ts +2 -0
  153. package/dist/render/scope-outlook.js +3 -0
  154. package/dist/render/spec-render.js +2 -2
  155. package/dist/resolution/package-manager.d.ts +33 -4
  156. package/dist/resolution/package-manager.js +210 -9
  157. package/dist/review-monitor/constants.d.ts +7 -0
  158. package/dist/review-monitor/constants.js +7 -0
  159. package/dist/review-monitor/github-lease.d.ts +98 -1
  160. package/dist/review-monitor/github-lease.js +186 -4
  161. package/dist/review-monitor/lease-comment.d.ts +36 -0
  162. package/dist/review-monitor/lease-comment.js +103 -2
  163. package/dist/run-summary/types.d.ts +2 -2
  164. package/dist/scm/build-command.d.ts +2 -2
  165. package/dist/scm/build-command.js +2 -2
  166. package/dist/scm/call-shape.d.ts +25 -0
  167. package/dist/scm/call-shape.js +59 -0
  168. package/dist/scm/call.d.ts +7 -4
  169. package/dist/scm/call.js +48 -9
  170. package/dist/scm/design-critique-chip.d.ts +1 -0
  171. package/dist/scm/design-critique-chip.js +22 -6
  172. package/dist/scm/gh-rest.d.ts +34 -13
  173. package/dist/scm/gh-rest.js +168 -15
  174. package/dist/scm/index.d.ts +2 -0
  175. package/dist/scm/index.js +2 -0
  176. package/dist/scm/readiness.d.ts +2 -0
  177. package/dist/scm/readiness.js +28 -4
  178. package/dist/scm/spawn-status.d.ts +33 -0
  179. package/dist/scm/spawn-status.js +53 -0
  180. package/dist/scope/acceptance-evidence.d.ts +1 -1
  181. package/dist/scope/project-definition-sync.js +14 -7
  182. package/dist/scope/transition.js +4 -14
  183. package/dist/session/ac-pass-banking.d.ts +2 -2
  184. package/dist/session/ac-pass-banking.js +2 -2
  185. package/dist/session/git.d.ts +24 -0
  186. package/dist/session/git.js +138 -2
  187. package/dist/session/host-session-owner.d.ts +53 -0
  188. package/dist/session/host-session-owner.js +89 -0
  189. package/dist/session/index.d.ts +1 -0
  190. package/dist/session/index.js +1 -0
  191. package/dist/session/occupancy.d.ts +259 -13
  192. package/dist/session/occupancy.js +877 -33
  193. package/dist/session/ritual-sentinel.d.ts +5 -0
  194. package/dist/session/ritual-sentinel.js +5 -0
  195. package/dist/session/session-ready.d.ts +5 -1
  196. package/dist/session/session-ready.js +98 -8
  197. package/dist/session/session-start.d.ts +5 -1
  198. package/dist/session/session-start.js +32 -17
  199. package/dist/session/toolchain-preflight.d.ts +13 -3
  200. package/dist/session/toolchain-preflight.js +93 -18
  201. package/dist/session/verify-ac-session-cache.d.ts +2 -2
  202. package/dist/session/verify-ac-session-cache.js +2 -2
  203. package/dist/session/verify-session-ritual.d.ts +11 -0
  204. package/dist/session/verify-session-ritual.js +60 -15
  205. package/dist/slice/constants.d.ts +1 -1
  206. package/dist/spec-authority/constants.d.ts +10 -2
  207. package/dist/spec-authority/constants.js +53 -8
  208. package/dist/spec-authority/resolver.d.ts +3 -0
  209. package/dist/spec-authority/resolver.js +55 -7
  210. package/dist/subprocess/max-buffer.d.ts +14 -0
  211. package/dist/subprocess/max-buffer.js +15 -0
  212. package/dist/swarm/complete-cohort.d.ts +2 -0
  213. package/dist/swarm/complete-cohort.js +21 -13
  214. package/dist/swarm/index.d.ts +1 -0
  215. package/dist/swarm/index.js +1 -0
  216. package/dist/swarm/launch-cli.js +53 -33
  217. package/dist/swarm/launch.d.ts +8 -0
  218. package/dist/swarm/launch.js +63 -43
  219. package/dist/swarm/pre-dispatch-cli.js +2 -1
  220. package/dist/swarm/pre-dispatch.js +10 -1
  221. package/dist/swarm/subagent-status-dir.d.ts +27 -0
  222. package/dist/swarm/subagent-status-dir.js +42 -0
  223. package/dist/swarm/worktrees.d.ts +17 -3
  224. package/dist/swarm/worktrees.js +48 -4
  225. package/dist/triage/evaluate/worktrees.js +153 -6
  226. package/dist/triage/scope/mutations-core.d.ts +2 -6
  227. package/dist/triage/scope/mutations-core.js +23 -55
  228. package/dist/triage/scope-drift/add-ignore.js +5 -39
  229. package/dist/triage/subscribe/index.d.ts +2 -6
  230. package/dist/triage/subscribe/index.js +15 -65
  231. package/dist/triage/welcome/writers.js +15 -74
  232. package/dist/umbrella-current-shape/index.d.ts +51 -3
  233. package/dist/umbrella-current-shape/index.js +106 -18
  234. package/dist/validate-content/deposit-required.d.ts +39 -0
  235. package/dist/validate-content/deposit-required.js +147 -0
  236. package/dist/validate-content/index.d.ts +1 -0
  237. package/dist/validate-content/index.js +1 -0
  238. package/dist/validate-content/validate-links.d.ts +2 -3
  239. package/dist/validate-content/validate-links.js +29 -2
  240. package/dist/vbrief-activate/activate.d.ts +7 -2
  241. package/dist/vbrief-activate/activate.js +26 -13
  242. package/dist/vbrief-build/index.d.ts +2 -1
  243. package/dist/vbrief-build/index.js +2 -1
  244. package/dist/vbrief-build/parity-scenarios.js +5 -5
  245. package/dist/vbrief-build/project-definition-io.d.ts +63 -3
  246. package/dist/vbrief-build/project-definition-io.js +357 -31
  247. package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
  248. package/dist/vbrief-build/project-definition-mutation.js +43 -0
  249. package/dist/vbrief-validate/conformance.d.ts +1 -0
  250. package/dist/vbrief-validate/conformance.js +85 -8
  251. package/dist/vbrief-validate/main.js +2 -0
  252. package/dist/vbrief-validate/precutover.js +5 -10
  253. package/dist/verify-ac/clauses.d.ts +62 -2
  254. package/dist/verify-ac/clauses.js +183 -111
  255. package/dist/verify-ac/evaluate.d.ts +9 -0
  256. package/dist/verify-ac/evaluate.js +32 -9
  257. package/dist/verify-ac/index.d.ts +1 -1
  258. package/dist/verify-ac/index.js +1 -1
  259. package/dist/verify-env/node-runtime.d.ts +8 -4
  260. package/dist/verify-env/node-runtime.js +9 -6
  261. package/dist/verify-env/toolchain-check.d.ts +35 -3
  262. package/dist/verify-env/toolchain-check.js +155 -36
  263. package/dist/verify-source/deposit-closure.d.ts +23 -0
  264. package/dist/verify-source/deposit-closure.js +162 -0
  265. package/dist/verify-source/index.d.ts +2 -0
  266. package/dist/verify-source/index.js +2 -0
  267. package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
  268. package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
  269. package/dist/verify-source/semantic-single-source.d.ts +36 -0
  270. package/dist/verify-source/semantic-single-source.js +349 -0
  271. package/package.json +7 -3
@@ -0,0 +1,278 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { REPO_RE } from "../cache/constants.js";
4
+ import { defaultRunGh } from "../pr-protected-issues/gh.js";
5
+ import { defaultWhich, resolveBinary } from "../scm/binary.js";
6
+ import { ScmStubError } from "../scm/errors.js";
7
+ import { restIssueListOpenInventory } from "../scm/gh-rest.js";
8
+ import { CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE } from "../triage/queue/constants.js";
9
+ /**
10
+ * A cache hit older than this is no longer evidence (#3767). The measured
11
+ * defect was a ~12 h old cached `open` suppressing the live read that would
12
+ * have corrected it; 15 min keeps warm-cache offline runs working while making
13
+ * an overnight entry non-authoritative.
14
+ */
15
+ export const ISSUE_CACHE_MAX_AGE_MS = 15 * 60 * 1000;
16
+ /**
17
+ * Latency budget for scoped `--issue N`: one authoritative issue read plus at
18
+ * most one linked-PR read. Measured ~0.75 s per `gh api` read; the headroom
19
+ * covers a slow network or a `ghx` proxy hop.
20
+ */
21
+ export const SCOPED_LATENCY_BUDGET_MS = 5_000;
22
+ /**
23
+ * Latency budget for the unscoped aggregate sweep. One complete open-issue
24
+ * inventory measured 4.2 s constant on deftai/directive (560 open issues),
25
+ * against 14.2 s and 76.6 s for per-brief reads at the WIP cap of 20 on two
26
+ * hosts. The budget is sized for the inventory plus a few confirming reads and
27
+ * keeps the gate inside the fast-preflight tier.
28
+ */
29
+ export const AGGREGATE_LATENCY_BUDGET_MS = 15_000;
30
+ /** `resolveBinary` view that hides `ghx`, so this gate reads through plain `gh` when it exists. */
31
+ const GH_ONLY_WHICH = (name) => (name === "ghx" ? null : defaultWhich(name));
32
+ /**
33
+ * Pin plain `gh` for this gate's authoritative reads (#3767). `resolveBinary`
34
+ * prefers `ghx` — a cached GET proxy — so an unpinned "live" read is itself a
35
+ * cache. When `gh` is absent we still run, and report the proxy caveat rather
36
+ * than claiming freshness we cannot deliver. Whether `ghx` should be preferred
37
+ * at all is #3737.
38
+ */
39
+ export function makeGateRunner() {
40
+ let pinned = null;
41
+ try {
42
+ pinned = resolveBinary(GH_ONLY_WHICH);
43
+ }
44
+ catch {
45
+ pinned = null;
46
+ }
47
+ if (pinned === null) {
48
+ return { runGh: defaultRunGh, proxied: true };
49
+ }
50
+ const binary = pinned;
51
+ return { runGh: (cmd) => defaultRunGh(cmd, binary), proxied: false };
52
+ }
53
+ function isSafeRepo(repo) {
54
+ return REPO_RE.test(repo);
55
+ }
56
+ function readJson(path) {
57
+ try {
58
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
59
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
60
+ ? parsed
61
+ : null;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ function cacheEntryDir(projectRoot, ref) {
68
+ const [owner, name] = ref.repo.split("/", 2);
69
+ if (!owner || !name) {
70
+ return null;
71
+ }
72
+ return join(projectRoot, CACHE_DIR_NAME, CACHE_SOURCE_GITHUB_ISSUE, owner, name, String(ref.number));
73
+ }
74
+ function cacheEntryAgeMs(entryDir, rawPath, nowMs) {
75
+ const meta = readJson(join(entryDir, "meta.json"));
76
+ const stamp = meta?.fetched_at ?? meta?.cached_at ?? meta?.updated_at;
77
+ if (typeof stamp === "string") {
78
+ const parsed = Date.parse(stamp);
79
+ if (Number.isFinite(parsed)) {
80
+ return Math.max(0, nowMs - parsed);
81
+ }
82
+ }
83
+ try {
84
+ return Math.max(0, nowMs - statSync(rawPath).mtimeMs);
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ const CACHE_MISS = { state: null, ageMs: null };
91
+ /** Read the triage-cache issue state together with the entry's age (#3767). */
92
+ export function readCachedIssue(projectRoot, ref, nowMs) {
93
+ const entryDir = cacheEntryDir(projectRoot, ref);
94
+ if (entryDir === null) {
95
+ return CACHE_MISS;
96
+ }
97
+ const rawPath = join(entryDir, "raw.json");
98
+ if (!existsSync(rawPath)) {
99
+ return CACHE_MISS;
100
+ }
101
+ const raw = readJson(rawPath);
102
+ if (raw === null) {
103
+ return CACHE_MISS;
104
+ }
105
+ const state = typeof raw.state === "string" ? raw.state.toLowerCase() : "";
106
+ if (state !== "open" && state !== "closed") {
107
+ return CACHE_MISS;
108
+ }
109
+ return { state, ageMs: cacheEntryAgeMs(entryDir, rawPath, nowMs) };
110
+ }
111
+ /** Authoritative single-issue REST read. Returns `null` on any failure. */
112
+ export function fetchIssueStateLive(ref, runGh) {
113
+ const result = runGh(["gh", "api", `repos/${ref.repo}/issues/${ref.number}`]);
114
+ if (result.returncode !== 0) {
115
+ return null;
116
+ }
117
+ try {
118
+ const payload = JSON.parse(result.stdout);
119
+ if (payload === null || typeof payload !== "object") {
120
+ return null;
121
+ }
122
+ const state = payload.state;
123
+ return state === "open" || state === "closed" ? state : null;
124
+ }
125
+ catch {
126
+ return null;
127
+ }
128
+ }
129
+ /**
130
+ * One complete open-issue inventory per repo per run (#3767, reusing #3752).
131
+ * `restIssueListOpenInventory` fails closed on command failure, non-array JSON,
132
+ * malformed rows, buffer exhaustion, and pagination cap — so absence from a
133
+ * successful inventory really does mean "not open", and a truncated read is an
134
+ * error rather than a silent "closed".
135
+ */
136
+ export class OpenIssueInventory {
137
+ runGh;
138
+ byRepo = new Map();
139
+ confirmations = new Map();
140
+ constructor(runGh) {
141
+ this.runGh = runGh;
142
+ }
143
+ lookup(repo) {
144
+ const memo = this.byRepo.get(repo);
145
+ if (memo !== undefined) {
146
+ return memo;
147
+ }
148
+ const resolved = this.fetch(repo);
149
+ this.byRepo.set(repo, resolved);
150
+ return resolved;
151
+ }
152
+ /**
153
+ * Confirm an inventory absence with one authoritative read, reused for the
154
+ * rest of this run when several briefs name the same issue (#3767).
155
+ */
156
+ confirm(ref) {
157
+ const key = `${ref.repo}#${ref.number}`;
158
+ if (this.confirmations.has(key)) {
159
+ return this.confirmations.get(key) ?? null;
160
+ }
161
+ const confirmed = fetchIssueStateLive(ref, this.runGh);
162
+ this.confirmations.set(key, confirmed);
163
+ return confirmed;
164
+ }
165
+ fetch(repo) {
166
+ try {
167
+ const rows = restIssueListOpenInventory(repo, {
168
+ runGhApiFn: (args) => this.runGh(["gh", "api", ...args]),
169
+ });
170
+ const numbers = new Set();
171
+ for (const row of rows) {
172
+ const n = row.number;
173
+ if (typeof n === "number" && Number.isInteger(n)) {
174
+ numbers.add(n);
175
+ }
176
+ }
177
+ return { numbers };
178
+ }
179
+ catch (err) {
180
+ if (err instanceof ScmStubError) {
181
+ throw err;
182
+ }
183
+ const message = err instanceof Error ? err.message : String(err);
184
+ return { error: message.split("\n")[0] ?? "unknown error" };
185
+ }
186
+ }
187
+ }
188
+ function unverified(detail) {
189
+ return { state: null, basis: "unverified", detail };
190
+ }
191
+ function freshOrNull(cached) {
192
+ return cached.state !== null && cached.ageMs !== null && cached.ageMs <= ISSUE_CACHE_MAX_AGE_MS
193
+ ? { state: cached.state, ageMs: cached.ageMs }
194
+ : null;
195
+ }
196
+ function fromCache(fresh) {
197
+ return { state: fresh.state, basis: "cache", cacheAgeMs: fresh.ageMs };
198
+ }
199
+ function staleDetail(cached) {
200
+ const age = cached.ageMs === null ? "unknown age" : formatAge(cached.ageMs);
201
+ return `cached ${cached.state} is ${age} old (max ${formatAge(ISSUE_CACHE_MAX_AGE_MS)})`;
202
+ }
203
+ /** Human-readable duration for basis reporting: `42s`, `9m`, `12h`, `3d`. */
204
+ export function formatAge(ms) {
205
+ const seconds = Math.round(ms / 1000);
206
+ if (seconds < 60) {
207
+ return `${seconds}s`;
208
+ }
209
+ const minutes = Math.round(seconds / 60);
210
+ if (minutes < 60) {
211
+ return `${minutes}m`;
212
+ }
213
+ const hours = Math.round(minutes / 60);
214
+ return hours < 48 ? `${hours}h` : `${Math.round(hours / 24)}d`;
215
+ }
216
+ /**
217
+ * Scoped `--issue N`: a proof obligation before DONE, so read authoritatively
218
+ * first and accept a cache hit only inside the freshness bound. Unknown stays
219
+ * fail-closed at the call site.
220
+ */
221
+ export function resolveIssueStateScoped(ref, ctx) {
222
+ if (!isSafeRepo(ref.repo)) {
223
+ return unverified(`reference repo '${ref.repo}' is not a valid owner/repo slug`);
224
+ }
225
+ if (!ctx.skipGh) {
226
+ const live = fetchIssueStateLive(ref, ctx.runGh);
227
+ if (live !== null) {
228
+ return { state: live, basis: "live" };
229
+ }
230
+ }
231
+ const cached = readCachedIssue(ctx.projectRoot, ref, ctx.nowMs);
232
+ const fresh = freshOrNull(cached);
233
+ if (fresh !== null) {
234
+ return fromCache(fresh);
235
+ }
236
+ if (cached.state !== null) {
237
+ return unverified(`${staleDetail(cached)} and the authoritative read was unavailable`);
238
+ }
239
+ return unverified(ctx.skipGh ? "--skip-gh with no cache entry" : "live issue read failed and no cache entry");
240
+ }
241
+ /**
242
+ * Unscoped aggregate: resolve every reference from one complete open-issue
243
+ * inventory rather than a live read per brief. Membership means open. Absence
244
+ * means "not open", which is the direction that tells an operator to run
245
+ * `scope:complete`, so it is confirmed by one authoritative read before the
246
+ * gate acts on it.
247
+ */
248
+ export function resolveIssueStateAggregate(ref, ctx) {
249
+ if (!isSafeRepo(ref.repo)) {
250
+ return unverified(`reference repo '${ref.repo}' is not a valid owner/repo slug`);
251
+ }
252
+ const cached = readCachedIssue(ctx.projectRoot, ref, ctx.nowMs);
253
+ const fresh = freshOrNull(cached);
254
+ if (ctx.skipGh) {
255
+ if (fresh !== null) {
256
+ return fromCache(fresh);
257
+ }
258
+ return unverified(cached.state === null
259
+ ? "--skip-gh with no cache entry"
260
+ : `--skip-gh and ${staleDetail(cached)}`);
261
+ }
262
+ const inventory = ctx.inventory.lookup(ref.repo);
263
+ if ("error" in inventory) {
264
+ if (fresh !== null) {
265
+ return fromCache(fresh);
266
+ }
267
+ return unverified(`open-issue inventory unavailable: ${inventory.error}`);
268
+ }
269
+ if (inventory.numbers.has(ref.number)) {
270
+ return { state: "open", basis: "inventory" };
271
+ }
272
+ const confirmed = ctx.inventory.confirm(ref);
273
+ if (confirmed !== null) {
274
+ return { state: confirmed, basis: "live" };
275
+ }
276
+ return unverified("absent from the open-issue inventory but the confirming read failed");
277
+ }
278
+ //# sourceMappingURL=issue-state.js.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Cursor managed-runtime probe and explicit host-gh selection (#3859).
3
+ *
4
+ * `CURSOR_AGENT` is set by local desktop Cursor, by Cursor-managed cloud VMs,
5
+ * and by Windows "My Machines" workers alike, so it cannot decide the runtime on
6
+ * its own. Two routes were considered and rejected:
7
+ *
8
+ * - Dropping the `CURSOR_AGENT` hop grants host credentials on a managed VM
9
+ * by marker *absence*.
10
+ * - `process.platform === "win32"` is not an invariant. Cursor runs Windows
11
+ * My Machines workers under a cloud agent loop, and "managed VMs are Ubuntu"
12
+ * is a versioned fact about a third party's fleet, not a runtime property.
13
+ *
14
+ * What remains is a genuine asymmetry:
15
+ *
16
+ * - Cursor-managed VMs serve a local metadata API on `CURSOR_AGENT_SOCKET`
17
+ * whose `agent/runtime` is always `managed`. A successful read is a
18
+ * *positive* assertion of managed cloud, so it is a sound deny signal.
19
+ * - There is no positive local-desktop signal. Absence of the socket means
20
+ * "not managed, or unreachable" and MUST NOT select host credentials --
21
+ * that is the marker-absence grant this module exists to prevent. Absence
22
+ * leaves the runtime ambiguous, and an ambiguous runtime requires an
23
+ * explicit operator or dispatcher selection.
24
+ *
25
+ * Both probe error directions are safe. A false `managed` denies host
26
+ * credentials (fail closed). A failed read leaves the runtime ambiguous, which
27
+ * preserves today's behaviour unless an operator opted in.
28
+ */
29
+ /** Env var carrying the Cursor managed-runtime metadata socket path. */
30
+ export declare const MANAGED_RUNTIME_SOCKET_ENV = "CURSOR_AGENT_SOCKET";
31
+ /** Metadata API route reporting the runtime kind. */
32
+ export declare const MANAGED_RUNTIME_PATH = "/agent/runtime";
33
+ /** The only `agent/runtime` value Cursor-managed VMs report. */
34
+ export declare const MANAGED_RUNTIME_VALUE = "managed";
35
+ /**
36
+ * Env var an operator or dispatcher sets to resolve an ambiguous Cursor
37
+ * runtime. Named for the `github_auth_mode` dispatch-envelope field so a
38
+ * dispatcher exports the same label it already records.
39
+ */
40
+ export declare const GITHUB_AUTH_MODE_ENV = "DEFT_GITHUB_AUTH_MODE";
41
+ /** Stable reason ids naming why a runtime mode was chosen (#3859). */
42
+ export declare const RUNTIME_REASON_CI_MARKER = "ci-marker";
43
+ export declare const RUNTIME_REASON_MANAGED_RUNTIME_PROBE = "cursor-managed-runtime-probe";
44
+ export declare const RUNTIME_REASON_CURSOR_SANDBOX_MARKER = "cursor-sandbox-marker";
45
+ export declare const RUNTIME_REASON_CURSOR_MARKER_AMBIGUOUS = "cursor-marker-runtime-ambiguous";
46
+ export declare const RUNTIME_REASON_EXPLICIT_HOST_GH = "explicit-host-gh-selection";
47
+ export declare const RUNTIME_REASON_NO_RUNTIME_MARKER = "no-runtime-marker";
48
+ export type ManagedRuntimeVerdict = "managed" | "not-managed" | "unavailable";
49
+ export interface ManagedRuntimeProbeResult {
50
+ /** `managed` is the only verdict that may deny host credentials. */
51
+ readonly verdict: ManagedRuntimeVerdict;
52
+ readonly socketPath: string | null;
53
+ /** Short non-secret diagnostic for operator-visible output. */
54
+ readonly detail: string;
55
+ }
56
+ export type ManagedRuntimeProbe = (environ: Readonly<Record<string, string | undefined>>) => ManagedRuntimeProbeResult;
57
+ /**
58
+ * True when a metadata body reports the managed runtime.
59
+ *
60
+ * Accepts a JSON object carrying a `runtime` (or `agentRuntime` / `agent_runtime`)
61
+ * field and a bare string body, because the exact response shape is documented
62
+ * only as "`agent/runtime` is always `managed`". Over-matching here denies host
63
+ * credentials, which is the safe direction.
64
+ */
65
+ export declare function bodyReportsManagedRuntime(body: string): boolean;
66
+ /** Shape the reader child writes to stdout. */
67
+ interface ProbeResponse {
68
+ readonly status?: number;
69
+ readonly body?: string;
70
+ readonly error?: string;
71
+ }
72
+ /**
73
+ * Interpret the reader child's stdout, or null when it is not a usable object.
74
+ *
75
+ * Exported for the same reason as `bodyReportsManagedRuntime`: it is the parsing
76
+ * step and it has to be provably total. `JSON.parse("null")` returns null
77
+ * without throwing, so a bare try/catch around the parse would still leave the
78
+ * field reads that follow able to throw -- and a probe that throws aborts
79
+ * classification instead of failing closed.
80
+ */
81
+ export declare function parseProbeResponse(stdout: string): ProbeResponse | null;
82
+ /** Clear the probe memo. Test seam only. */
83
+ export declare function resetManagedRuntimeProbeCache(): void;
84
+ /** Default managed-runtime probe. Never throws. */
85
+ export declare const probeManagedRuntime: ManagedRuntimeProbe;
86
+ /**
87
+ * True when an operator or dispatcher explicitly selected host-gh for this
88
+ * execution environment. This resolves an *ambiguous* runtime only; it can
89
+ * never override a positive managed-runtime read.
90
+ */
91
+ export declare function hasExplicitHostGhSelection(environ: Readonly<Record<string, string | undefined>>): boolean;
92
+ /** Remediation naming the opt-in, for operator-visible not-ready output. */
93
+ export declare const EXPLICIT_SELECTION_REMEDIATION: string;
94
+ export {};
95
+ //# sourceMappingURL=cursor-managed-runtime.d.ts.map
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Cursor managed-runtime probe and explicit host-gh selection (#3859).
3
+ *
4
+ * `CURSOR_AGENT` is set by local desktop Cursor, by Cursor-managed cloud VMs,
5
+ * and by Windows "My Machines" workers alike, so it cannot decide the runtime on
6
+ * its own. Two routes were considered and rejected:
7
+ *
8
+ * - Dropping the `CURSOR_AGENT` hop grants host credentials on a managed VM
9
+ * by marker *absence*.
10
+ * - `process.platform === "win32"` is not an invariant. Cursor runs Windows
11
+ * My Machines workers under a cloud agent loop, and "managed VMs are Ubuntu"
12
+ * is a versioned fact about a third party's fleet, not a runtime property.
13
+ *
14
+ * What remains is a genuine asymmetry:
15
+ *
16
+ * - Cursor-managed VMs serve a local metadata API on `CURSOR_AGENT_SOCKET`
17
+ * whose `agent/runtime` is always `managed`. A successful read is a
18
+ * *positive* assertion of managed cloud, so it is a sound deny signal.
19
+ * - There is no positive local-desktop signal. Absence of the socket means
20
+ * "not managed, or unreachable" and MUST NOT select host credentials --
21
+ * that is the marker-absence grant this module exists to prevent. Absence
22
+ * leaves the runtime ambiguous, and an ambiguous runtime requires an
23
+ * explicit operator or dispatcher selection.
24
+ *
25
+ * Both probe error directions are safe. A false `managed` denies host
26
+ * credentials (fail closed). A failed read leaves the runtime ambiguous, which
27
+ * preserves today's behaviour unless an operator opted in.
28
+ */
29
+ import { spawnSync } from "node:child_process";
30
+ /**
31
+ * The `host-gh` auth-mode label. Duplicated from intake/github-auth-modes.ts
32
+ * rather than imported: that module imports the runtime classifier, which
33
+ * imports this one.
34
+ */
35
+ const HOST_GH_SELECTION = "host-gh";
36
+ /** Env var carrying the Cursor managed-runtime metadata socket path. */
37
+ export const MANAGED_RUNTIME_SOCKET_ENV = "CURSOR_AGENT_SOCKET";
38
+ /** Metadata API route reporting the runtime kind. */
39
+ export const MANAGED_RUNTIME_PATH = "/agent/runtime";
40
+ /** The only `agent/runtime` value Cursor-managed VMs report. */
41
+ export const MANAGED_RUNTIME_VALUE = "managed";
42
+ /**
43
+ * Env var an operator or dispatcher sets to resolve an ambiguous Cursor
44
+ * runtime. Named for the `github_auth_mode` dispatch-envelope field so a
45
+ * dispatcher exports the same label it already records.
46
+ */
47
+ export const GITHUB_AUTH_MODE_ENV = "DEFT_GITHUB_AUTH_MODE";
48
+ /** Stable reason ids naming why a runtime mode was chosen (#3859). */
49
+ export const RUNTIME_REASON_CI_MARKER = "ci-marker";
50
+ export const RUNTIME_REASON_MANAGED_RUNTIME_PROBE = "cursor-managed-runtime-probe";
51
+ export const RUNTIME_REASON_CURSOR_SANDBOX_MARKER = "cursor-sandbox-marker";
52
+ export const RUNTIME_REASON_CURSOR_MARKER_AMBIGUOUS = "cursor-marker-runtime-ambiguous";
53
+ export const RUNTIME_REASON_EXPLICIT_HOST_GH = "explicit-host-gh-selection";
54
+ export const RUNTIME_REASON_NO_RUNTIME_MARKER = "no-runtime-marker";
55
+ /**
56
+ * Read the metadata API in a child process.
57
+ *
58
+ * The readiness and classification paths are synchronous throughout, and Node
59
+ * cannot read an HTTP-over-unix-socket response synchronously in-process. This
60
+ * runs only when `CURSOR_AGENT_SOCKET` is set, so the common local case pays
61
+ * nothing.
62
+ */
63
+ const READER_SCRIPT = `
64
+ const http = require("node:http");
65
+ const done = (payload) => { process.stdout.write(JSON.stringify(payload)); };
66
+ const req = http.request(
67
+ {
68
+ socketPath: process.env.DEFT_PROBE_SOCKET,
69
+ path: process.env.DEFT_PROBE_PATH,
70
+ method: "GET",
71
+ timeout: 2000,
72
+ },
73
+ (res) => {
74
+ let body = "";
75
+ res.setEncoding("utf8");
76
+ res.on("data", (chunk) => { body += chunk; if (body.length > 4096) req.destroy(); });
77
+ res.on("end", () => done({ status: res.statusCode, body }));
78
+ },
79
+ );
80
+ req.on("timeout", () => { req.destroy(new Error("timeout")); });
81
+ req.on("error", (err) => done({ error: String((err && err.message) || err) }));
82
+ req.end();
83
+ `;
84
+ function unavailable(socketPath, detail) {
85
+ return { verdict: "unavailable", socketPath, detail };
86
+ }
87
+ /**
88
+ * True when a metadata body reports the managed runtime.
89
+ *
90
+ * Accepts a JSON object carrying a `runtime` (or `agentRuntime` / `agent_runtime`)
91
+ * field and a bare string body, because the exact response shape is documented
92
+ * only as "`agent/runtime` is always `managed`". Over-matching here denies host
93
+ * credentials, which is the safe direction.
94
+ */
95
+ export function bodyReportsManagedRuntime(body) {
96
+ const text = body.trim();
97
+ if (text.length === 0)
98
+ return false;
99
+ if (text.toLowerCase() === MANAGED_RUNTIME_VALUE)
100
+ return true;
101
+ let parsed;
102
+ try {
103
+ parsed = JSON.parse(text);
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ if (typeof parsed === "string")
109
+ return parsed.trim().toLowerCase() === MANAGED_RUNTIME_VALUE;
110
+ if (typeof parsed !== "object" || parsed === null)
111
+ return false;
112
+ const record = parsed;
113
+ for (const key of ["runtime", "agentRuntime", "agent_runtime"]) {
114
+ const value = record[key];
115
+ if (typeof value === "string" && value.trim().toLowerCase() === MANAGED_RUNTIME_VALUE) {
116
+ return true;
117
+ }
118
+ }
119
+ return false;
120
+ }
121
+ /**
122
+ * Interpret the reader child's stdout, or null when it is not a usable object.
123
+ *
124
+ * Exported for the same reason as `bodyReportsManagedRuntime`: it is the parsing
125
+ * step and it has to be provably total. `JSON.parse("null")` returns null
126
+ * without throwing, so a bare try/catch around the parse would still leave the
127
+ * field reads that follow able to throw -- and a probe that throws aborts
128
+ * classification instead of failing closed.
129
+ */
130
+ export function parseProbeResponse(stdout) {
131
+ let parsed;
132
+ try {
133
+ parsed = JSON.parse(stdout);
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
139
+ return null;
140
+ return parsed;
141
+ }
142
+ /**
143
+ * Per-process memo of *positive* managed reads, keyed by socket path.
144
+ *
145
+ * Only the `managed` verdict is memoized, and that asymmetry is the safety
146
+ * property. A managed VM cannot stop being managed mid-process, so holding that
147
+ * deny costs nothing and keeps classification to one child spawn per socket.
148
+ *
149
+ * Every other verdict says only that the API did not assert `managed` on that
150
+ * attempt: the socket may not have been listening yet, or the read may have
151
+ * failed outright. Memoizing one would let a transient miss suppress a later
152
+ * deny -- a managed VM whose first probe lost the startup race would stay
153
+ * ambiguous for the life of the process, and an explicit `host-gh` selection
154
+ * would then hand it host credentials. Re-probing an ambiguous runtime costs a
155
+ * child spawn bounded by the timeouts below; caching one is unbounded in the
156
+ * only direction that matters.
157
+ */
158
+ const managedVerdictMemo = new Map();
159
+ /** Clear the probe memo. Test seam only. */
160
+ export function resetManagedRuntimeProbeCache() {
161
+ managedVerdictMemo.clear();
162
+ }
163
+ /** Default managed-runtime probe. Never throws. */
164
+ export const probeManagedRuntime = (environ) => {
165
+ const socketPath = (environ[MANAGED_RUNTIME_SOCKET_ENV] ?? "").trim();
166
+ if (socketPath.length === 0) {
167
+ return unavailable(null, `${MANAGED_RUNTIME_SOCKET_ENV} is not set; runtime not asserted`);
168
+ }
169
+ const memoized = managedVerdictMemo.get(socketPath);
170
+ if (memoized !== undefined)
171
+ return memoized;
172
+ const result = readManagedRuntime(socketPath);
173
+ if (result.verdict === "managed")
174
+ managedVerdictMemo.set(socketPath, result);
175
+ return result;
176
+ };
177
+ function readManagedRuntime(socketPath) {
178
+ let result;
179
+ try {
180
+ result = spawnSync(process.execPath, ["-e", READER_SCRIPT], {
181
+ env: {
182
+ ...process.env,
183
+ DEFT_PROBE_SOCKET: socketPath,
184
+ DEFT_PROBE_PATH: MANAGED_RUNTIME_PATH,
185
+ },
186
+ encoding: "utf8",
187
+ timeout: 3000,
188
+ stdio: ["ignore", "pipe", "pipe"],
189
+ windowsHide: true,
190
+ });
191
+ }
192
+ catch (err) {
193
+ const message = err instanceof Error ? err.message : String(err);
194
+ return unavailable(socketPath, `metadata probe failed to start: ${message}`);
195
+ }
196
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
197
+ if (stdout.trim().length === 0) {
198
+ return unavailable(socketPath, `${MANAGED_RUNTIME_PATH} returned no response`);
199
+ }
200
+ const payload = parseProbeResponse(stdout);
201
+ if (payload === null) {
202
+ return unavailable(socketPath, `${MANAGED_RUNTIME_PATH} response was unparseable`);
203
+ }
204
+ if (typeof payload.error === "string") {
205
+ return unavailable(socketPath, `${MANAGED_RUNTIME_PATH} unreachable: ${payload.error}`);
206
+ }
207
+ if (payload.status !== 200) {
208
+ return unavailable(socketPath, `${MANAGED_RUNTIME_PATH} returned status ${payload.status}`);
209
+ }
210
+ if (bodyReportsManagedRuntime(payload.body ?? "")) {
211
+ return {
212
+ verdict: "managed",
213
+ socketPath,
214
+ detail: `${MANAGED_RUNTIME_PATH} reported ${MANAGED_RUNTIME_VALUE}`,
215
+ };
216
+ }
217
+ return {
218
+ verdict: "not-managed",
219
+ socketPath,
220
+ detail: `${MANAGED_RUNTIME_PATH} did not report ${MANAGED_RUNTIME_VALUE}`,
221
+ };
222
+ }
223
+ /**
224
+ * True when an operator or dispatcher explicitly selected host-gh for this
225
+ * execution environment. This resolves an *ambiguous* runtime only; it can
226
+ * never override a positive managed-runtime read.
227
+ */
228
+ export function hasExplicitHostGhSelection(environ) {
229
+ return (environ[GITHUB_AUTH_MODE_ENV] ?? "").trim().toLowerCase() === HOST_GH_SELECTION;
230
+ }
231
+ /** Remediation naming the opt-in, for operator-visible not-ready output. */
232
+ export const EXPLICIT_SELECTION_REMEDIATION = `Remediation for an ambiguous Cursor runtime (#3859):\n` +
233
+ ` - CURSOR_AGENT is set but the ${MANAGED_RUNTIME_SOCKET_ENV} metadata API did not report\n` +
234
+ ` "${MANAGED_RUNTIME_VALUE}", so this runtime could be local desktop, a self-hosted\n` +
235
+ ` worker, or an unreachable managed VM. Deft will not guess from the OS.\n` +
236
+ ` - On a machine you control, where the host gh credential store is the right\n` +
237
+ ` credential, opt in explicitly: set ${GITHUB_AUTH_MODE_ENV}=${HOST_GH_SELECTION}\n` +
238
+ ` - Dispatchers should export the same github_auth_mode label they record in the\n` +
239
+ ` dispatch envelope\n` +
240
+ ` - A positive managed-runtime read always wins; this opt-in cannot override it`;
241
+ //# sourceMappingURL=cursor-managed-runtime.js.map
@@ -1,6 +1,7 @@
1
1
  export * from "./agents-consumer-header.js";
2
2
  export * from "./agents-md.js";
3
3
  export * from "./constants.js";
4
+ export * from "./cursor-managed-runtime.js";
4
5
  export * from "./host-content-surface.js";
5
6
  export * from "./ip-risk.js";
6
7
  export * from "./linear-scan.js";
@@ -1,6 +1,7 @@
1
1
  export * from "./agents-consumer-header.js";
2
2
  export * from "./agents-md.js";
3
3
  export * from "./constants.js";
4
+ export * from "./cursor-managed-runtime.js";
4
5
  export * from "./host-content-surface.js";
5
6
  export * from "./ip-risk.js";
6
7
  export * from "./linear-scan.js";
@@ -1,3 +1,4 @@
1
+ import { type ManagedRuntimeProbe } from "./cursor-managed-runtime.js";
1
2
  import { type ShellContext } from "./shell-context.js";
2
3
  export interface UidMapEntry {
3
4
  readonly insideId: number;
@@ -14,6 +15,8 @@ export interface RuntimeCapabilityReport {
14
15
  readonly hostPlatform: NodeJS.Platform;
15
16
  readonly shell: ShellContext;
16
17
  readonly runtimeMode: string;
18
+ /** Stable id naming why `runtimeMode` was chosen (#3859). */
19
+ readonly runtimeModeReason: string;
17
20
  readonly identityKind: string;
18
21
  readonly effectiveUid: number | null;
19
22
  readonly effectiveUsername: string | null;
@@ -33,7 +36,24 @@ export declare function classifyIdentityKind(options: {
33
36
  effectiveUid: number | null;
34
37
  sandboxUidRemap: boolean;
35
38
  }): string;
36
- export declare function classifyRuntimeMode(environ: Readonly<Record<string, string>>, sandboxUidRemap: boolean): string;
39
+ export interface RuntimeClassification {
40
+ readonly mode: string;
41
+ readonly reason: string;
42
+ }
43
+ /**
44
+ * Classify runtime mode and name the reason for the verdict (#3859).
45
+ *
46
+ * Ordering matches intake/platform-capabilities.ts: CI markers, then a positive
47
+ * managed-runtime read, then sandbox, then the ambiguous Cursor hop. The Cursor
48
+ * hop previously ran first here; it now runs last so neither a CI marker nor a
49
+ * managed read can be overridden by an explicit host-gh selection.
50
+ */
51
+ export declare function classifyRuntime(environ: Readonly<Record<string, string>>, sandboxUidRemap: boolean, options?: {
52
+ managedRuntimeProbe?: ManagedRuntimeProbe;
53
+ }): RuntimeClassification;
54
+ export declare function classifyRuntimeMode(environ: Readonly<Record<string, string>>, sandboxUidRemap: boolean, options?: {
55
+ managedRuntimeProbe?: ManagedRuntimeProbe;
56
+ }): string;
37
57
  export interface ProbeRuntimeOptions {
38
58
  readonly environ?: Readonly<Record<string, string>>;
39
59
  readonly platform?: NodeJS.Platform;
@@ -43,6 +63,8 @@ export interface ProbeRuntimeOptions {
43
63
  readonly effectiveUidOverride?: number | null;
44
64
  readonly effectiveUsername?: string | null;
45
65
  readonly getuid?: () => number;
66
+ /** Injectable managed-runtime probe (#3859). Defaults to the metadata read. */
67
+ readonly managedRuntimeProbe?: ManagedRuntimeProbe;
46
68
  }
47
69
  export declare function probeRuntimeCapabilities(options?: ProbeRuntimeOptions): RuntimeCapabilityReport;
48
70
  export declare function getPlatformCapabilities(options?: ProbeRuntimeOptions): RuntimeCapabilityReport;