@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.
- package/dist/architecture/sor-preflight.js +2 -0
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +232 -0
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +69 -2
- package/dist/hooks/dispatcher.js +609 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +75 -7
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +9 -18
- package/dist/policy/write-fence.js +3 -0
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/preflight-cache/evaluate.js +9 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +53 -12
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +19 -0
- package/dist/release/types.d.ts +14 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +38 -0
- package/dist/scm/design-critique-chip.js +187 -0
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +4 -1
- package/dist/scm/readiness.js +35 -5
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +3 -1
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +42 -0
- package/dist/session/git.js +219 -3
- package/dist/session/occupancy.d.ts +239 -12
- package/dist/session/occupancy.js +820 -27
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +17 -1
- package/dist/session/session-start.js +49 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +33 -3
- package/dist/session/verify-session-ritual.js +89 -19
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +77 -0
- package/dist/swarm/launch.js +248 -38
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +21 -7
- package/dist/triage/help/registry-data.js +53 -5
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/dist/xbrief-migrate/agents-header.js +69 -7
- 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
|
|
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
|
|
85
|
-
|
|
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
|
|
94
|
-
if (
|
|
95
|
-
parts.push(
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10
|
-
|
|
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
|