@deftai/directive-core 0.106.0 → 0.108.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 (249) hide show
  1. package/dist/architecture/sor-preflight.js +2 -0
  2. package/dist/check/cached-orchestrator.js +28 -3
  3. package/dist/check/gate-lists.js +2 -0
  4. package/dist/check/named-cause.js +18 -3
  5. package/dist/codebase/provider.js +7 -1
  6. package/dist/delivery-attempt/handoff.js +1 -1
  7. package/dist/design-critique/citation-grammar.d.ts +53 -0
  8. package/dist/design-critique/citation-grammar.js +303 -0
  9. package/dist/design-critique/completed-arc-record.d.ts +62 -0
  10. package/dist/design-critique/completed-arc-record.js +232 -0
  11. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  12. package/dist/design-critique/exclusive-chip.js +68 -0
  13. package/dist/design-critique/parent-audit.d.ts +59 -0
  14. package/dist/design-critique/parent-audit.js +121 -0
  15. package/dist/doctor/checks.d.ts +6 -0
  16. package/dist/doctor/checks.js +35 -0
  17. package/dist/freshness/bind.d.ts +32 -3
  18. package/dist/freshness/bind.js +58 -12
  19. package/dist/hooks/classify/host-session-identity.d.ts +76 -0
  20. package/dist/hooks/classify/host-session-identity.js +470 -0
  21. package/dist/hooks/classify/index.d.ts +3 -2
  22. package/dist/hooks/classify/index.js +3 -2
  23. package/dist/hooks/classify/paths.d.ts +7 -0
  24. package/dist/hooks/classify/paths.js +35 -0
  25. package/dist/hooks/classify/stdin.d.ts +5 -0
  26. package/dist/hooks/classify/stdin.js +27 -0
  27. package/dist/hooks/dest-form.d.ts +46 -0
  28. package/dist/hooks/dest-form.js +575 -0
  29. package/dist/hooks/dispatcher.d.ts +69 -2
  30. package/dist/hooks/dispatcher.js +609 -45
  31. package/dist/hooks/fixtures/cases.d.ts +16 -2
  32. package/dist/hooks/fixtures/cases.js +175 -0
  33. package/dist/hooks/index.d.ts +1 -0
  34. package/dist/hooks/index.js +1 -0
  35. package/dist/hooks/scope.js +3 -1
  36. package/dist/init-deposit/agent-hooks.d.ts +2 -1
  37. package/dist/init-deposit/agent-hooks.js +8 -1
  38. package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
  39. package/dist/init-deposit/hook-runtime-travel.js +269 -0
  40. package/dist/intake/clause-derivation.js +9 -3
  41. package/dist/intake/github-auth-modes-cli.js +14 -2
  42. package/dist/intake/github-auth-modes.d.ts +54 -9
  43. package/dist/intake/github-auth-modes.js +326 -123
  44. package/dist/intake/issue-ingest.js +62 -2
  45. package/dist/intake/platform-capabilities.d.ts +9 -2
  46. package/dist/intake/platform-capabilities.js +38 -9
  47. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  48. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  49. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  50. package/dist/lifecycle/completed-write-guard.js +373 -0
  51. package/dist/lifecycle/index.d.ts +1 -0
  52. package/dist/lifecycle/index.js +1 -0
  53. package/dist/literal-acceptance/capture.d.ts +16 -0
  54. package/dist/literal-acceptance/capture.js +75 -7
  55. package/dist/literal-acceptance/evaluate.js +25 -3
  56. package/dist/literal-acceptance/index.d.ts +2 -2
  57. package/dist/literal-acceptance/index.js +2 -2
  58. package/dist/literal-acceptance/run.js +9 -3
  59. package/dist/literal-acceptance/types.d.ts +6 -0
  60. package/dist/literal-acceptance/types.js +6 -0
  61. package/dist/orphan-active/evaluate.d.ts +25 -0
  62. package/dist/orphan-active/evaluate.js +193 -128
  63. package/dist/orphan-active/index.d.ts +1 -0
  64. package/dist/orphan-active/index.js +1 -0
  65. package/dist/orphan-active/issue-state.d.ts +121 -0
  66. package/dist/orphan-active/issue-state.js +278 -0
  67. package/dist/platform/cursor-managed-runtime.d.ts +95 -0
  68. package/dist/platform/cursor-managed-runtime.js +241 -0
  69. package/dist/platform/index.d.ts +1 -0
  70. package/dist/platform/index.js +1 -0
  71. package/dist/platform/platform-capabilities.d.ts +23 -1
  72. package/dist/platform/platform-capabilities.js +55 -15
  73. package/dist/policy/ceremony-dial.js +5 -9
  74. package/dist/policy/host-hooks.js +4 -9
  75. package/dist/policy/org-force-on-migration.js +5 -10
  76. package/dist/policy/plan-extensions.d.ts +14 -2
  77. package/dist/policy/plan-extensions.js +24 -3
  78. package/dist/policy/product-signal.js +5 -10
  79. package/dist/policy/require-human-merge.js +5 -9
  80. package/dist/policy/resolve.js +77 -16
  81. package/dist/policy/runtime-authority.d.ts +18 -0
  82. package/dist/policy/runtime-authority.js +11 -0
  83. package/dist/policy/value-feedback.js +9 -18
  84. package/dist/policy/write-fence.js +3 -0
  85. package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
  86. package/dist/pr-closeout-attestable/evaluate.js +306 -0
  87. package/dist/pr-closeout-attestable/index.d.ts +2 -0
  88. package/dist/pr-closeout-attestable/index.js +2 -0
  89. package/dist/pr-protected-issues/gh.d.ts +6 -2
  90. package/dist/pr-protected-issues/gh.js +7 -3
  91. package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
  92. package/dist/pr-wait-mergeable/cascade.js +21 -1
  93. package/dist/pr-wait-mergeable/types.d.ts +2 -0
  94. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/wrappers.js +11 -0
  96. package/dist/preflight/evaluate.d.ts +15 -0
  97. package/dist/preflight/evaluate.js +29 -3
  98. package/dist/preflight-cache/evaluate.js +9 -3
  99. package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
  100. package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
  101. package/dist/product-first-done-gate/evaluate.js +53 -12
  102. package/dist/release/cli-drift-report.d.ts +68 -0
  103. package/dist/release/cli-drift-report.js +189 -0
  104. package/dist/release/index.d.ts +1 -0
  105. package/dist/release/index.js +1 -0
  106. package/dist/release/issue-state-fetch.d.ts +5 -3
  107. package/dist/release/issue-state-fetch.js +86 -21
  108. package/dist/release/native-steps.js +1 -0
  109. package/dist/release/pipeline.js +19 -0
  110. package/dist/release/types.d.ts +14 -0
  111. package/dist/render/constants.d.ts +4 -0
  112. package/dist/render/constants.js +11 -8
  113. package/dist/render/export-spec.js +31 -5
  114. package/dist/render/index.d.ts +1 -1
  115. package/dist/render/index.js +1 -1
  116. package/dist/render/prd-render.d.ts +4 -1
  117. package/dist/render/prd-render.js +82 -23
  118. package/dist/render/project-render.js +14 -7
  119. package/dist/render/scope-outlook.d.ts +2 -0
  120. package/dist/render/scope-outlook.js +3 -0
  121. package/dist/render/spec-render.js +2 -2
  122. package/dist/resolution/package-manager.d.ts +33 -4
  123. package/dist/resolution/package-manager.js +210 -9
  124. package/dist/review-monitor/constants.d.ts +7 -0
  125. package/dist/review-monitor/constants.js +7 -0
  126. package/dist/review-monitor/github-lease.d.ts +98 -1
  127. package/dist/review-monitor/github-lease.js +182 -1
  128. package/dist/review-monitor/lease-comment.d.ts +36 -0
  129. package/dist/review-monitor/lease-comment.js +103 -2
  130. package/dist/scm/call.js +12 -3
  131. package/dist/scm/design-critique-chip.d.ts +38 -0
  132. package/dist/scm/design-critique-chip.js +187 -0
  133. package/dist/scm/gh-rest.d.ts +9 -1
  134. package/dist/scm/gh-rest.js +103 -0
  135. package/dist/scm/index.d.ts +1 -0
  136. package/dist/scm/index.js +1 -0
  137. package/dist/scm/main.d.ts +3 -0
  138. package/dist/scm/main.js +15 -1
  139. package/dist/scm/readiness-cli.d.ts +2 -0
  140. package/dist/scm/readiness-cli.js +42 -1
  141. package/dist/scm/readiness.d.ts +4 -1
  142. package/dist/scm/readiness.js +35 -5
  143. package/dist/scope/decompose.js +2 -1
  144. package/dist/scope/delivery-evidence.d.ts +27 -1
  145. package/dist/scope/index.d.ts +1 -0
  146. package/dist/scope/index.js +1 -0
  147. package/dist/scope/lifecycle-write.d.ts +24 -0
  148. package/dist/scope/lifecycle-write.js +64 -0
  149. package/dist/scope/main.js +3 -1
  150. package/dist/scope/project-definition-sync.js +14 -7
  151. package/dist/scope/transition.js +90 -11
  152. package/dist/scope-provenance/evaluate.js +5 -1
  153. package/dist/session/git.d.ts +42 -0
  154. package/dist/session/git.js +219 -3
  155. package/dist/session/occupancy.d.ts +239 -12
  156. package/dist/session/occupancy.js +820 -27
  157. package/dist/session/ritual-sentinel.d.ts +5 -0
  158. package/dist/session/ritual-sentinel.js +5 -0
  159. package/dist/session/session-ready.d.ts +5 -1
  160. package/dist/session/session-ready.js +98 -8
  161. package/dist/session/session-start.d.ts +17 -1
  162. package/dist/session/session-start.js +49 -17
  163. package/dist/session/toolchain-preflight.d.ts +13 -3
  164. package/dist/session/toolchain-preflight.js +93 -18
  165. package/dist/session/verify-session-ritual.d.ts +33 -3
  166. package/dist/session/verify-session-ritual.js +89 -19
  167. package/dist/slice/constants.d.ts +1 -1
  168. package/dist/spec-authority/constants.d.ts +10 -2
  169. package/dist/spec-authority/constants.js +53 -8
  170. package/dist/spec-authority/resolver.d.ts +3 -0
  171. package/dist/spec-authority/resolver.js +55 -7
  172. package/dist/subprocess/max-buffer.d.ts +14 -0
  173. package/dist/subprocess/max-buffer.js +15 -0
  174. package/dist/swarm/complete-cohort.d.ts +2 -0
  175. package/dist/swarm/complete-cohort.js +21 -13
  176. package/dist/swarm/index.d.ts +1 -0
  177. package/dist/swarm/index.js +1 -0
  178. package/dist/swarm/launch-cli.js +53 -33
  179. package/dist/swarm/launch.d.ts +77 -0
  180. package/dist/swarm/launch.js +248 -38
  181. package/dist/swarm/pre-dispatch-cli.js +2 -1
  182. package/dist/swarm/pre-dispatch.js +10 -1
  183. package/dist/swarm/subagent-status-dir.d.ts +27 -0
  184. package/dist/swarm/subagent-status-dir.js +42 -0
  185. package/dist/swarm/worktrees.d.ts +17 -3
  186. package/dist/swarm/worktrees.js +48 -4
  187. package/dist/triage/actions/index.d.ts +4 -0
  188. package/dist/triage/actions/index.js +6 -0
  189. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  190. package/dist/triage/evaluate/evaluate.js +169 -0
  191. package/dist/triage/evaluate/github.d.ts +16 -0
  192. package/dist/triage/evaluate/github.js +153 -0
  193. package/dist/triage/evaluate/index.d.ts +9 -0
  194. package/dist/triage/evaluate/index.js +8 -0
  195. package/dist/triage/evaluate/paths.d.ts +7 -0
  196. package/dist/triage/evaluate/paths.js +25 -0
  197. package/dist/triage/evaluate/sink.d.ts +5 -0
  198. package/dist/triage/evaluate/sink.js +52 -0
  199. package/dist/triage/evaluate/types.d.ts +93 -0
  200. package/dist/triage/evaluate/types.js +13 -0
  201. package/dist/triage/evaluate/validity.d.ts +8 -0
  202. package/dist/triage/evaluate/validity.js +87 -0
  203. package/dist/triage/evaluate/value.d.ts +12 -0
  204. package/dist/triage/evaluate/value.js +33 -0
  205. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  206. package/dist/triage/evaluate/wip-census.js +39 -0
  207. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  208. package/dist/triage/evaluate/worktrees.js +68 -0
  209. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  210. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  211. package/dist/triage/help/registry-data.d.ts +21 -7
  212. package/dist/triage/help/registry-data.js +53 -5
  213. package/dist/triage/index.d.ts +1 -0
  214. package/dist/triage/index.js +1 -0
  215. package/dist/triage/scope/mutations-core.d.ts +2 -6
  216. package/dist/triage/scope/mutations-core.js +23 -55
  217. package/dist/triage/scope-drift/add-ignore.js +5 -39
  218. package/dist/triage/subscribe/index.d.ts +2 -6
  219. package/dist/triage/subscribe/index.js +15 -65
  220. package/dist/triage/welcome/writers.js +15 -74
  221. package/dist/value/feedback-file.d.ts +17 -2
  222. package/dist/value/feedback-file.js +80 -6
  223. package/dist/vbrief-build/index.d.ts +2 -1
  224. package/dist/vbrief-build/index.js +2 -1
  225. package/dist/vbrief-build/parity-scenarios.js +5 -5
  226. package/dist/vbrief-build/project-definition-io.d.ts +63 -3
  227. package/dist/vbrief-build/project-definition-io.js +357 -31
  228. package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
  229. package/dist/vbrief-build/project-definition-mutation.js +43 -0
  230. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  231. package/dist/vbrief-reconcile/labels.js +30 -0
  232. package/dist/vbrief-validate/conformance.d.ts +1 -0
  233. package/dist/vbrief-validate/conformance.js +85 -8
  234. package/dist/vbrief-validate/main.js +2 -0
  235. package/dist/vbrief-validate/precutover.js +5 -10
  236. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  237. package/dist/vbrief-validation/story-quality.js +8 -1
  238. package/dist/verify-ac/clauses.d.ts +62 -2
  239. package/dist/verify-ac/clauses.js +183 -111
  240. package/dist/verify-ac/index.d.ts +1 -1
  241. package/dist/verify-ac/index.js +1 -1
  242. package/dist/verify-env/node-runtime.d.ts +8 -4
  243. package/dist/verify-env/node-runtime.js +9 -6
  244. package/dist/verify-env/toolchain-check.d.ts +35 -3
  245. package/dist/verify-env/toolchain-check.js +155 -36
  246. package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
  247. package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
  248. package/dist/xbrief-migrate/agents-header.js +69 -7
  249. package/package.json +7 -3
@@ -7,12 +7,18 @@ export const KNOWN_GITHUB_AUTH_MODES = new Set([
7
7
  GITHUB_AUTH_MODE_INJECTED_TOKEN,
8
8
  GITHUB_AUTH_MODE_HOST_GH,
9
9
  ]);
10
- export const DEFAULT_VALIDATION_REPO = "deftai/directive";
10
+ export const PRINCIPAL_KIND_USER = "user";
11
11
  export const FAILURE_MISSING_INJECTED_TOKEN = "missing_injected_token";
12
12
  export const FAILURE_GH_AUTH = "gh_auth_failed";
13
13
  export const FAILURE_API_UNREACHABLE = "api_unreachable";
14
14
  export const FAILURE_REPO_ACCESS = "repo_access_denied";
15
15
  export const FAILURE_INVALID_MODE = "invalid_auth_mode";
16
+ export const FAILURE_MISSING_EXPECTED_PRINCIPAL = "missing_expected_principal";
17
+ export const FAILURE_PRINCIPAL_MISMATCH = "principal_mismatch";
18
+ export const FAILURE_MISSING_TARGET_REPO = "missing_target_repo";
19
+ export const FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE = "installation_identity_unverifiable";
20
+ export const ENV_EXPECTED_GITHUB_LOGIN = "DEFT_EXPECTED_GITHUB_LOGIN";
21
+ export const INSTALLATION_IDENTITY_ISSUE_URL = "https://github.com/deftai/directive/issues/3693";
16
22
  const INJECTED_TOKEN_ENV_VARS = ["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN"];
17
23
  const SANDBOX_REMEDIATION = "Remediation options for worker sandbox GitHub auth failures:\n" +
18
24
  " - Run the GitHub step with full-access execution\n" +
@@ -22,6 +28,15 @@ const REPO_ACCESS_REMEDIATION = "Remediation options for repo-access failures:\n
22
28
  " - Confirm the worker credential can read the target repository\n" +
23
29
  " - Run the GitHub step with full-access execution if host gh has access\n" +
24
30
  " - Use injected-token handoff scoped to the required repository";
31
+ const PRINCIPAL_REMEDIATION = "Remediation for GitHub worker-principal failures:\n" +
32
+ " - User-bearing credential: pass expectedPrincipal.login or set DEFT_EXPECTED_GITHUB_LOGIN\n" +
33
+ " - Do not treat a /user 403 on an installation token as API unreachability\n" +
34
+ " - GitHub App installation identity cannot be verified from the token; see #3693";
35
+ const INSTALLATION_IDENTITY_REMEDIATION = "A GitHub App installation token cannot prove which App it belongs to.\n" +
36
+ `Tracked in ${INSTALLATION_IDENTITY_ISSUE_URL}. Do not treat a /user 403 as API unreachability.`;
37
+ const TARGET_REPO_REMEDIATION = "Remediation for missing target repository:\n" +
38
+ " - Pass repo as owner/repo, or set GH_REPO / GITHUB_REPOSITORY, or run inside a git checkout with origin\n" +
39
+ " - Do not fall back to a hard-coded public default";
25
40
  export function findInjectedToken(environ) {
26
41
  for (const name of INJECTED_TOKEN_ENV_VARS) {
27
42
  const value = environ[name]?.trim() ?? "";
@@ -63,6 +78,113 @@ function defaultRunGh(args, environ) {
63
78
  return { args: [binary, ...args], returncode: 1, stdout: "", stderr: message };
64
79
  }
65
80
  }
81
+ export function defaultReadGitOriginUrl(cwd) {
82
+ try {
83
+ const result = spawnSync("git", ["remote", "get-url", "origin"], {
84
+ cwd,
85
+ encoding: "utf8",
86
+ timeout: 10_000,
87
+ stdio: ["ignore", "pipe", "pipe"],
88
+ });
89
+ if ((result.status ?? 1) !== 0) {
90
+ return null;
91
+ }
92
+ const url = (typeof result.stdout === "string" ? result.stdout : "").trim();
93
+ return url.length > 0 ? url : null;
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ export function parseOwnerRepoSlug(input) {
100
+ const raw = input.trim();
101
+ if (raw.length === 0) {
102
+ return null;
103
+ }
104
+ const direct = raw.match(/^([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)$/);
105
+ if (direct) {
106
+ return `${direct[1]}/${direct[2]}`;
107
+ }
108
+ const stripped = raw.replace(/\.git$/i, "");
109
+ // SCP form including GitHub Enterprise: git@host:owner/repo
110
+ if (!stripped.includes("://")) {
111
+ const scp = stripped.match(/:([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)$/);
112
+ if (scp) {
113
+ return `${scp[1]}/${scp[2]}`;
114
+ }
115
+ }
116
+ const fromUrl = stripped.match(/[:/]([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)$/);
117
+ if (fromUrl) {
118
+ return `${fromUrl[1]}/${fromUrl[2]}`;
119
+ }
120
+ return null;
121
+ }
122
+ export function deriveValidationRepo(options) {
123
+ if (options.repo !== undefined && options.repo !== null) {
124
+ const parsed = parseOwnerRepoSlug(options.repo);
125
+ if (parsed === null) {
126
+ return {
127
+ ok: false,
128
+ detail: `invalid repository slug: ${JSON.stringify(options.repo)} (expected owner/repo)`,
129
+ };
130
+ }
131
+ return { ok: true, repo: parsed };
132
+ }
133
+ const env = options.environ ?? {};
134
+ const fromEnv = (env.GH_REPO ?? env.GITHUB_REPOSITORY ?? "").trim();
135
+ if (fromEnv.length > 0) {
136
+ const parsed = parseOwnerRepoSlug(fromEnv);
137
+ if (parsed === null) {
138
+ return {
139
+ ok: false,
140
+ detail: `invalid repository slug from GH_REPO/GITHUB_REPOSITORY: ${JSON.stringify(fromEnv)}`,
141
+ };
142
+ }
143
+ return { ok: true, repo: parsed };
144
+ }
145
+ const remote = options.gitRemoteUrl !== undefined && options.gitRemoteUrl !== null
146
+ ? options.gitRemoteUrl
147
+ : (options.readGitRemote ?? defaultReadGitOriginUrl)(options.cwd);
148
+ if (typeof remote === "string" && remote.trim().length > 0) {
149
+ const parsed = parseOwnerRepoSlug(remote);
150
+ if (parsed === null) {
151
+ return {
152
+ ok: false,
153
+ detail: `invalid git origin URL for repository derivation: ${JSON.stringify(remote.trim())}`,
154
+ };
155
+ }
156
+ return { ok: true, repo: parsed };
157
+ }
158
+ return {
159
+ ok: false,
160
+ detail: "cannot derive target repository; pass repo as owner/repo, set GH_REPO or GITHUB_REPOSITORY, or run inside a git checkout with origin",
161
+ };
162
+ }
163
+ export function resolveExpectedGithubWorkerPrincipal(environ, explicit) {
164
+ if (explicit === null) {
165
+ return null;
166
+ }
167
+ if (explicit !== undefined) {
168
+ return normalizeExpectedPrincipal(explicit);
169
+ }
170
+ const login = environ[ENV_EXPECTED_GITHUB_LOGIN]?.trim() ?? "";
171
+ if (login.length > 0) {
172
+ return { kind: PRINCIPAL_KIND_USER, login };
173
+ }
174
+ return null;
175
+ }
176
+ function normalizeExpectedPrincipal(principal) {
177
+ if (principal.kind !== PRINCIPAL_KIND_USER) {
178
+ return {
179
+ error: `unknown expected principal kind ${pyRepr(principal.kind)}`,
180
+ };
181
+ }
182
+ const login = principal.login.trim();
183
+ if (login.length === 0) {
184
+ return { error: "user principal requires a non-empty login" };
185
+ }
186
+ return { kind: PRINCIPAL_KIND_USER, login };
187
+ }
66
188
  function splitRepo(repo) {
67
189
  const idx = repo.indexOf("/");
68
190
  if (idx <= 0 || idx >= repo.length - 1) {
@@ -76,13 +198,30 @@ function sandboxRemediation(runtimeMode, failureKind) {
76
198
  }
77
199
  if (failureKind === FAILURE_GH_AUTH ||
78
200
  failureKind === FAILURE_API_UNREACHABLE ||
79
- failureKind === FAILURE_REPO_ACCESS) {
201
+ failureKind === FAILURE_REPO_ACCESS ||
202
+ failureKind === FAILURE_MISSING_EXPECTED_PRINCIPAL ||
203
+ failureKind === FAILURE_PRINCIPAL_MISMATCH ||
204
+ failureKind === FAILURE_MISSING_TARGET_REPO ||
205
+ failureKind === FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE) {
80
206
  return SANDBOX_REMEDIATION;
81
207
  }
82
208
  return null;
83
209
  }
84
- function repoAccessRemediation(failureKind) {
85
- return failureKind === FAILURE_REPO_ACCESS ? REPO_ACCESS_REMEDIATION : null;
210
+ function extraRemediation(failureKind) {
211
+ if (failureKind === FAILURE_REPO_ACCESS) {
212
+ return REPO_ACCESS_REMEDIATION;
213
+ }
214
+ if (failureKind === FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE) {
215
+ return INSTALLATION_IDENTITY_REMEDIATION;
216
+ }
217
+ if (failureKind === FAILURE_MISSING_EXPECTED_PRINCIPAL ||
218
+ failureKind === FAILURE_PRINCIPAL_MISMATCH) {
219
+ return PRINCIPAL_REMEDIATION;
220
+ }
221
+ if (failureKind === FAILURE_MISSING_TARGET_REPO) {
222
+ return TARGET_REPO_REMEDIATION;
223
+ }
224
+ return null;
86
225
  }
87
226
  function mergeRemediation(runtimeMode, failureKind) {
88
227
  const parts = [];
@@ -90,9 +229,9 @@ function mergeRemediation(runtimeMode, failureKind) {
90
229
  if (sandbox !== null) {
91
230
  parts.push(sandbox);
92
231
  }
93
- const repo = repoAccessRemediation(failureKind);
94
- if (repo !== null && !parts.includes(repo)) {
95
- parts.push(repo);
232
+ const extra = extraRemediation(failureKind);
233
+ if (extra !== null && !parts.includes(extra)) {
234
+ parts.push(extra);
96
235
  }
97
236
  return parts.length > 0 ? parts.join("\n\n") : null;
98
237
  }
@@ -118,147 +257,193 @@ function parseLogin(stdout) {
118
257
  }
119
258
  return null;
120
259
  }
121
- export function validateInjectedTokenMode(environ, options = {}) {
122
- const runner = options.runGh ?? defaultRunGh;
123
- const repo = options.repo ?? DEFAULT_VALIDATION_REPO;
124
- const runtimeMode = options.runtimeMode ?? null;
125
- const token = findInjectedToken(environ);
126
- if (token === null) {
127
- return {
128
- ok: false,
129
- githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
130
- runtimeMode,
131
- failureKind: FAILURE_MISSING_INJECTED_TOKEN,
132
- detail: "injected-token mode requires GH_TOKEN, GITHUB_TOKEN, or GH_ENTERPRISE_TOKEN; host gh credential store is not used",
133
- remediation: null,
134
- login: null,
135
- };
260
+ function combinedGhText(proc) {
261
+ return `${proc.stdout}\n${proc.stderr}`;
262
+ }
263
+ export function isInstallationUserEndpointInapplicable(proc) {
264
+ const blob = combinedGhText(proc).toLowerCase();
265
+ return (blob.includes("resource not accessible by integration") ||
266
+ blob.includes("not accessible by integration"));
267
+ }
268
+ function clipFailureText(text, max = 240) {
269
+ const compact = text.replace(/\s+/g, " ").trim();
270
+ if (compact.length === 0) {
271
+ return "";
136
272
  }
137
- const authStatus = runner(["auth", "status"], environ);
138
- if (authStatus.returncode !== 0) {
139
- return {
140
- ok: false,
141
- githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
142
- runtimeMode,
143
- failureKind: FAILURE_GH_AUTH,
144
- detail: "injected token present but gh auth status failed in worker",
145
- remediation: mergeRemediation(runtimeMode, FAILURE_GH_AUTH),
146
- login: null,
147
- };
273
+ if (compact.length <= max) {
274
+ return compact;
148
275
  }
149
- const userApi = runner(["api", "user", "--jq", ".login"], environ);
150
- if (userApi.returncode !== 0) {
151
- return {
152
- ok: false,
153
- githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
154
- runtimeMode,
155
- failureKind: FAILURE_API_UNREACHABLE,
156
- detail: "injected token present but GitHub API is unreachable",
157
- remediation: mergeRemediation(runtimeMode, FAILURE_API_UNREACHABLE),
158
- login: null,
159
- };
276
+ return `${compact.slice(0, max - 3)}...`;
277
+ }
278
+ function parseGhApiMessage(proc) {
279
+ for (const chunk of [proc.stdout, proc.stderr]) {
280
+ const text = chunk.trim();
281
+ if (text.length === 0) {
282
+ continue;
283
+ }
284
+ try {
285
+ const payload = JSON.parse(text);
286
+ if (payload !== null && typeof payload === "object" && !Array.isArray(payload)) {
287
+ const message = payload.message;
288
+ if (typeof message === "string" && message.length > 0) {
289
+ const status = payload.status;
290
+ if (typeof status === "string" || typeof status === "number") {
291
+ return `${message} (HTTP ${status})`;
292
+ }
293
+ return message;
294
+ }
295
+ }
296
+ }
297
+ catch {
298
+ // fall through to raw text
299
+ }
160
300
  }
161
- const login = parseLogin(userApi.stdout);
301
+ return null;
302
+ }
303
+ function isNetworkUnreachableText(text) {
304
+ return /timed out|timeout|econnrefused|enotfound|eai_again|network is unreachable|could not resolve host/i.test(text);
305
+ }
306
+ export function formatUserApiFailureDetail(mode, proc) {
307
+ const parsed = parseGhApiMessage(proc);
308
+ const raw = clipFailureText(combinedGhText(proc));
309
+ const cause = parsed ?? (raw.length > 0 ? raw : `exit ${proc.returncode}`);
310
+ const prefix = mode === GITHUB_AUTH_MODE_INJECTED_TOKEN
311
+ ? "injected token present but GitHub /user failed"
312
+ : "gh auth status passed but GitHub /user failed";
313
+ if (isNetworkUnreachableText(cause) || isNetworkUnreachableText(raw)) {
314
+ return `${prefix}: GitHub API is unreachable (${cause})`;
315
+ }
316
+ return `${prefix}: ${cause}`;
317
+ }
318
+ function emptyResult(mode, runtimeMode, failureKind, detail, options = {}) {
319
+ const ok = options.ok ?? false;
320
+ return {
321
+ ok,
322
+ githubAuthMode: mode,
323
+ runtimeMode,
324
+ failureKind: ok ? null : failureKind,
325
+ detail,
326
+ remediation: ok ? null : mergeRemediation(runtimeMode, failureKind ?? ""),
327
+ login: options.login ?? null,
328
+ principal: options.principal ?? null,
329
+ validationRepo: options.validationRepo ?? null,
330
+ };
331
+ }
332
+ function loginsMatch(expected, observed) {
333
+ return expected.localeCompare(observed, undefined, { sensitivity: "accent" }) === 0;
334
+ }
335
+ function failClosedInstallationCredential(mode, runtimeMode, repo) {
336
+ return emptyResult(mode, runtimeMode, FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE, "GitHub /user is inapplicable to a GitHub App installation credential (no authenticated user). " +
337
+ "The API is reachable. An installation token cannot disclose which App it belongs to, " +
338
+ `so identity cannot be verified. Deferred to ${INSTALLATION_IDENTITY_ISSUE_URL}.`, { validationRepo: repo });
339
+ }
340
+ function checkTargetRepoAccess(mode, runtimeMode, runner, environ, repo, login, principal) {
162
341
  const [owner, name] = splitRepo(repo);
163
342
  const repoApi = runner(["api", `repos/${owner}/${name}`], environ);
164
343
  if (repoApi.returncode !== 0) {
165
- return {
166
- ok: false,
167
- githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
168
- runtimeMode,
169
- failureKind: FAILURE_REPO_ACCESS,
170
- detail: `injected token can reach GitHub API but cannot access ${repo}`,
171
- remediation: mergeRemediation(runtimeMode, FAILURE_REPO_ACCESS),
344
+ const detail = mode === GITHUB_AUTH_MODE_INJECTED_TOKEN
345
+ ? `injected token can reach GitHub API but cannot access ${repo}`
346
+ : `GitHub API reachable but repository access failed for ${repo}`;
347
+ return emptyResult(mode, runtimeMode, FAILURE_REPO_ACCESS, detail, {
172
348
  login,
173
- };
349
+ principal,
350
+ validationRepo: repo,
351
+ });
174
352
  }
175
- return {
353
+ const okDetail = mode === GITHUB_AUTH_MODE_INJECTED_TOKEN
354
+ ? "injected-token mode validated in worker environment"
355
+ : "host-gh mode validated in worker environment";
356
+ return emptyResult(mode, runtimeMode, null, okDetail, {
176
357
  ok: true,
177
- githubAuthMode: GITHUB_AUTH_MODE_INJECTED_TOKEN,
178
- runtimeMode,
179
- failureKind: null,
180
- detail: "injected-token mode validated in worker environment",
181
- remediation: null,
182
358
  login,
183
- };
359
+ principal,
360
+ validationRepo: repo,
361
+ });
184
362
  }
185
- export function validateHostGhMode(environ, options = {}) {
363
+ function validateAfterAuth(mode, environ, options, runtimeMode, runner) {
364
+ const derived = deriveValidationRepo({
365
+ repo: options.repo,
366
+ environ,
367
+ gitRemoteUrl: options.gitRemoteUrl,
368
+ readGitRemote: options.readGitRemote,
369
+ cwd: options.cwd,
370
+ });
371
+ if (!derived.ok) {
372
+ return emptyResult(mode, runtimeMode, FAILURE_MISSING_TARGET_REPO, derived.detail);
373
+ }
374
+ const repo = derived.repo;
375
+ const expectedPrincipal = resolveExpectedGithubWorkerPrincipal(environ, options.expectedPrincipal);
376
+ if (expectedPrincipal !== null && "error" in expectedPrincipal && options.expectedPrincipal) {
377
+ return emptyResult(mode, runtimeMode, FAILURE_MISSING_EXPECTED_PRINCIPAL, expectedPrincipal.error, {
378
+ validationRepo: repo,
379
+ });
380
+ }
381
+ const userApi = runner(["api", "user"], environ);
382
+ if (userApi.returncode !== 0) {
383
+ if (isInstallationUserEndpointInapplicable(userApi)) {
384
+ return failClosedInstallationCredential(mode, runtimeMode, repo);
385
+ }
386
+ return emptyResult(mode, runtimeMode, FAILURE_API_UNREACHABLE, formatUserApiFailureDetail(mode, userApi), { validationRepo: repo });
387
+ }
388
+ const login = parseLogin(userApi.stdout);
389
+ if (login === null) {
390
+ return emptyResult(mode, runtimeMode, FAILURE_PRINCIPAL_MISMATCH, "GitHub /user succeeded but returned no login", { validationRepo: repo });
391
+ }
392
+ if (expectedPrincipal !== null && "error" in expectedPrincipal) {
393
+ return emptyResult(mode, runtimeMode, FAILURE_MISSING_EXPECTED_PRINCIPAL, expectedPrincipal.error, {
394
+ login,
395
+ validationRepo: repo,
396
+ });
397
+ }
398
+ if (expectedPrincipal !== null) {
399
+ if (!loginsMatch(expectedPrincipal.login, login)) {
400
+ return emptyResult(mode, runtimeMode, FAILURE_PRINCIPAL_MISMATCH, `identity mismatch: expected ${expectedPrincipal.login}, observed ${login}`, { login, validationRepo: repo });
401
+ }
402
+ }
403
+ const principal = { kind: PRINCIPAL_KIND_USER, login };
404
+ return checkTargetRepoAccess(mode, runtimeMode, runner, environ, repo, login, principal);
405
+ }
406
+ export function validateInjectedTokenMode(environ, options = {}) {
186
407
  const runner = options.runGh ?? defaultRunGh;
187
- const repo = options.repo ?? DEFAULT_VALIDATION_REPO;
188
408
  const runtimeMode = options.runtimeMode ?? null;
409
+ const tokenPresent = findInjectedToken(environ) !== null;
410
+ if (!tokenPresent) {
411
+ return emptyResult(GITHUB_AUTH_MODE_INJECTED_TOKEN, runtimeMode, FAILURE_MISSING_INJECTED_TOKEN, "injected-token mode requires GH_TOKEN, GITHUB_TOKEN, or GH_ENTERPRISE_TOKEN; host gh credential store is not used");
412
+ }
189
413
  const authStatus = runner(["auth", "status"], environ);
190
414
  if (authStatus.returncode !== 0) {
191
- return {
192
- ok: false,
193
- githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
194
- runtimeMode,
195
- failureKind: FAILURE_GH_AUTH,
196
- detail: "gh auth status failed in worker environment",
197
- remediation: mergeRemediation(runtimeMode, FAILURE_GH_AUTH),
198
- login: null,
199
- };
200
- }
201
- const userApi = runner(["api", "user", "--jq", ".login"], environ);
202
- if (userApi.returncode !== 0) {
203
- return {
204
- ok: false,
205
- githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
206
- runtimeMode,
207
- failureKind: FAILURE_API_UNREACHABLE,
208
- detail: "gh auth status passed but GitHub API is unreachable",
209
- remediation: mergeRemediation(runtimeMode, FAILURE_API_UNREACHABLE),
210
- login: null,
211
- };
415
+ return emptyResult(GITHUB_AUTH_MODE_INJECTED_TOKEN, runtimeMode, FAILURE_GH_AUTH, "injected token present but gh auth status failed in worker");
212
416
  }
213
- const [owner, name] = splitRepo(repo);
214
- const repoApi = runner(["api", `repos/${owner}/${name}`], environ);
215
- if (repoApi.returncode !== 0) {
216
- return {
217
- ok: false,
218
- githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
219
- runtimeMode,
220
- failureKind: FAILURE_REPO_ACCESS,
221
- detail: `GitHub API reachable but repository access failed for ${repo}`,
222
- remediation: mergeRemediation(runtimeMode, FAILURE_REPO_ACCESS),
223
- login: parseLogin(userApi.stdout),
224
- };
417
+ return validateAfterAuth(GITHUB_AUTH_MODE_INJECTED_TOKEN, environ, options, runtimeMode, runner);
418
+ }
419
+ export function validateHostGhMode(environ, options = {}) {
420
+ const runner = options.runGh ?? defaultRunGh;
421
+ const runtimeMode = options.runtimeMode ?? null;
422
+ const authStatus = runner(["auth", "status"], environ);
423
+ if (authStatus.returncode !== 0) {
424
+ return emptyResult(GITHUB_AUTH_MODE_HOST_GH, runtimeMode, FAILURE_GH_AUTH, "gh auth status failed in worker environment");
225
425
  }
226
- return {
227
- ok: true,
228
- githubAuthMode: GITHUB_AUTH_MODE_HOST_GH,
229
- runtimeMode,
230
- failureKind: null,
231
- detail: "host-gh mode validated in worker environment",
232
- remediation: null,
233
- login: parseLogin(userApi.stdout),
234
- };
426
+ return validateAfterAuth(GITHUB_AUTH_MODE_HOST_GH, environ, options, runtimeMode, runner);
235
427
  }
236
428
  export function validateGithubAuth(githubAuthMode, options = {}) {
237
429
  const env = options.environ ?? process.env;
238
430
  const runtimeMode = options.runtimeReport?.runtimeMode ?? null;
239
431
  if (!KNOWN_GITHUB_AUTH_MODES.has(githubAuthMode)) {
240
- return {
241
- ok: false,
242
- githubAuthMode,
243
- runtimeMode,
244
- failureKind: FAILURE_INVALID_MODE,
245
- detail: `unknown github_auth_mode ${pyRepr(githubAuthMode)}; expected one of ${pyRepr([...KNOWN_GITHUB_AUTH_MODES].sort())}`,
246
- remediation: null,
247
- login: null,
248
- };
249
- }
250
- if (githubAuthMode === GITHUB_AUTH_MODE_INJECTED_TOKEN) {
251
- return validateInjectedTokenMode(env, {
252
- repo: options.repo,
253
- runtimeMode,
254
- runGh: options.runGh,
255
- });
432
+ return emptyResult(githubAuthMode, runtimeMode, FAILURE_INVALID_MODE, `unknown github_auth_mode ${pyRepr(githubAuthMode)}; expected one of ${pyRepr([...KNOWN_GITHUB_AUTH_MODES].sort())}`);
256
433
  }
257
- return validateHostGhMode(env, {
434
+ const shared = {
258
435
  repo: options.repo,
259
436
  runtimeMode,
260
437
  runGh: options.runGh,
261
- });
438
+ expectedPrincipal: options.expectedPrincipal,
439
+ gitRemoteUrl: options.gitRemoteUrl,
440
+ readGitRemote: options.readGitRemote,
441
+ cwd: options.cwd,
442
+ };
443
+ if (githubAuthMode === GITHUB_AUTH_MODE_INJECTED_TOKEN) {
444
+ return validateInjectedTokenMode(env, shared);
445
+ }
446
+ return validateHostGhMode(env, shared);
262
447
  }
263
448
  export function validateGithubAuthForWorker(githubAuthMode = null, options = {}) {
264
449
  const report = options.runtimeReport ?? getPlatformCapabilities();
@@ -274,12 +459,30 @@ export function resultToDict(result) {
274
459
  detail: result.detail,
275
460
  remediation: result.remediation,
276
461
  login: result.login,
462
+ principal_kind: result.principal?.kind ?? null,
463
+ validation_repo: result.validationRepo,
277
464
  };
278
465
  }
466
+ function expectedPrincipalFromCliArgs(args) {
467
+ if (args.expectedPrincipal !== undefined) {
468
+ return args.expectedPrincipal ?? undefined;
469
+ }
470
+ const login = args.expectedLogin?.trim() ?? "";
471
+ if (login.length > 0) {
472
+ return { kind: PRINCIPAL_KIND_USER, login };
473
+ }
474
+ return undefined;
475
+ }
279
476
  export function githubAuthModesMain(args) {
477
+ const fromFlags = expectedPrincipalFromCliArgs(args);
478
+ if (fromFlags !== undefined && "error" in fromFlags) {
479
+ process.stderr.write(`${fromFlags.error}\n`);
480
+ return 2;
481
+ }
280
482
  const result = validateGithubAuthForWorker(args.githubAuthMode ?? null, {
281
- repo: args.repo ?? DEFAULT_VALIDATION_REPO,
483
+ repo: args.repo,
282
484
  runGh: args.runGh,
485
+ expectedPrincipal: fromFlags,
283
486
  });
284
487
  if (args.json) {
285
488
  process.stdout.write(`${JSON.stringify(resultToDict(result), null, 2)}\n`);
@@ -2,6 +2,7 @@ import { lstatSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSyn
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { cacheGet } from "../cache/operations.js";
4
4
  import { scan } from "../cache/scanner.js";
5
+ import { assertCompletedArcAllowsIngest, DesignCritiqueIngestBlockedError, } from "../design-critique/completed-arc-record.js";
5
6
  import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
6
7
  import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
7
8
  import { captureAndAttachLiteralAcceptance } from "../literal-acceptance/index.js";
@@ -703,6 +704,41 @@ function resolveIngestProjectRoot(vbriefDir, cwd) {
703
704
  }
704
705
  return null;
705
706
  }
707
+ function issueLabelNames(issue) {
708
+ const labels = issue.labels;
709
+ if (!Array.isArray(labels))
710
+ return [];
711
+ const names = [];
712
+ for (const lbl of labels) {
713
+ if (typeof lbl === "string") {
714
+ names.push(lbl);
715
+ continue;
716
+ }
717
+ if (lbl !== null && typeof lbl === "object" && !Array.isArray(lbl)) {
718
+ const name = lbl.name;
719
+ if (typeof name === "string")
720
+ names.push(name);
721
+ }
722
+ }
723
+ return names;
724
+ }
725
+ function threadCommentsFromIssue(issue) {
726
+ const raw = issue[ISSUE_COMMENT_THREAD_KEY];
727
+ if (!Array.isArray(raw))
728
+ return [];
729
+ const out = [];
730
+ for (const entry of raw) {
731
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry))
732
+ continue;
733
+ const rec = entry;
734
+ const id = Number(rec.id);
735
+ if (!Number.isSafeInteger(id) || id <= 0)
736
+ continue;
737
+ const body = typeof rec.body === "string" ? rec.body : "";
738
+ out.push({ id, body });
739
+ }
740
+ return out;
741
+ }
706
742
  export function ingestOne(issue, options) {
707
743
  const number = Number(issue.number);
708
744
  const refs = options.existingRefs ?? scanProvenanceRefs(options.vbriefDir);
@@ -719,6 +755,11 @@ export function ingestOne(issue, options) {
719
755
  cwd: options.cwd,
720
756
  cacheRoot: options.cacheRoot,
721
757
  });
758
+ assertCompletedArcAllowsIngest({
759
+ issueNumber: number,
760
+ labels: issueLabelNames(enriched),
761
+ comments: threadCommentsFromIssue(enriched),
762
+ });
722
763
  const emissionLayout = resolveIngestEmissionLayout(options.vbriefDir);
723
764
  const [vbrief, folder] = buildIssueVbrief(enriched, options.status, options.repoUrl, {
724
765
  infoRootKey: emissionLayout.infoRootKey,
@@ -774,6 +815,7 @@ export function ingestBulk(issues, options) {
774
815
  duplicate: [],
775
816
  dryrun: [],
776
817
  failed: [],
818
+ blocked: [],
777
819
  notices: [],
778
820
  };
779
821
  for (const issue of filtered) {
@@ -790,6 +832,11 @@ export function ingestBulk(issues, options) {
790
832
  process.stderr.write(`${exc.message}\n`);
791
833
  continue;
792
834
  }
835
+ if (exc instanceof DesignCritiqueIngestBlockedError) {
836
+ summary.blocked.push(`#${exc.issueNumber}`);
837
+ process.stderr.write(`${exc.message}\n`);
838
+ continue;
839
+ }
793
840
  throw exc;
794
841
  }
795
842
  const [result, path, msg] = ingested;
@@ -861,7 +908,8 @@ export function issueIngestMain(args) {
861
908
  const duplicate = summary.duplicate;
862
909
  const dryrun = summary.dryrun;
863
910
  const failed = summary.failed ?? [];
864
- process.stdout.write(`issue:ingest bulk summary: ${created.length} created, ${duplicate.length} duplicate, ${dryrun.length} dry-run, ${failed.length} refused (total considered: ${summary.total})\n`);
911
+ const blocked = summary.blocked ?? [];
912
+ process.stdout.write(`issue:ingest bulk summary: ${created.length} created, ${duplicate.length} duplicate, ${dryrun.length} dry-run, ${failed.length} refused, ${blocked.length} blocked (total considered: ${summary.total})\n`);
865
913
  for (const entry of created) {
866
914
  process.stdout.write(` CREATED ${entry}\n`);
867
915
  }
@@ -877,8 +925,16 @@ export function issueIngestMain(args) {
877
925
  for (const entry of failed) {
878
926
  process.stdout.write(` REFUSED ${entry} (quarantine scanner hard-fail; nothing written)\n`);
879
927
  }
928
+ for (const entry of blocked) {
929
+ process.stdout.write(` BLOCKED ${entry} (design-critique completed-arc record; nothing written)\n`);
930
+ }
880
931
  // #2306: fail closed on any quarantine hard-fail in the batch.
881
- return failed.length > 0 ? 2 : 0;
932
+ // Design-critique protocol holds are exit 1, not the scanner fatal path.
933
+ if (failed.length > 0)
934
+ return 2;
935
+ if (blocked.length > 0)
936
+ return 1;
937
+ return 0;
882
938
  }
883
939
  const issue = fetchIssue(repo, args.number, { cwd: projectRoot });
884
940
  if (issue === null) {
@@ -901,6 +957,10 @@ export function issueIngestMain(args) {
901
957
  process.stderr.write(`${exc.message}\n`);
902
958
  return 2;
903
959
  }
960
+ if (exc instanceof DesignCritiqueIngestBlockedError) {
961
+ process.stderr.write(`${exc.message}\n`);
962
+ return 1;
963
+ }
904
964
  throw exc;
905
965
  }
906
966
  process.stdout.write(`${msg}\n`);
@@ -1,11 +1,18 @@
1
1
  /** Minimal runtime capability probe for github_auth_modes (#1557a / #1784). */
2
+ import { type ManagedRuntimeProbe } from "../platform/cursor-managed-runtime.js";
2
3
  export declare const RUNTIME_MODE_LOCAL_UNSANDBOXED = "local-unsandboxed";
3
4
  export declare const RUNTIME_MODE_CURSOR_NATIVE_SANDBOX = "cursor-native-sandbox";
4
5
  export declare const RUNTIME_MODE_CLOUD_HEADLESS = "cloud-headless";
5
6
  export declare const KNOWN_RUNTIME_MODES: Set<string>;
6
7
  export interface RuntimeCapabilityReport {
7
8
  readonly runtimeMode: string;
9
+ /** Stable id naming why `runtimeMode` was chosen; surfaced by scm:status (#3859). */
10
+ readonly runtimeModeReason?: string;
8
11
  }
9
- export declare function probeRuntimeCapabilities(environ?: NodeJS.ProcessEnv): RuntimeCapabilityReport;
10
- export declare function getPlatformCapabilities(): RuntimeCapabilityReport;
12
+ export interface ProbeRuntimeCapabilityOptions {
13
+ /** Injectable managed-runtime probe (#3859). Defaults to the metadata read. */
14
+ readonly managedRuntimeProbe?: ManagedRuntimeProbe;
15
+ }
16
+ export declare function probeRuntimeCapabilities(environ?: NodeJS.ProcessEnv, options?: ProbeRuntimeCapabilityOptions): RuntimeCapabilityReport;
17
+ export declare function getPlatformCapabilities(options?: ProbeRuntimeCapabilityOptions): RuntimeCapabilityReport;
11
18
  //# sourceMappingURL=platform-capabilities.d.ts.map