@deftai/directive-content 0.55.1 → 0.56.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/.githooks/pre-commit +143 -0
- package/.githooks/pre-push +121 -0
- package/QUICK-START.md +13 -3
- package/Taskfile.yml +934 -0
- package/UPGRADING.md +82 -11
- package/events/README.md +3 -3
- package/package.json +5 -4
- package/packs/skills/skills-pack-0.1.json +22 -22
- package/scripts/_agents_md.py +494 -0
- package/scripts/_cache_fetch.py +635 -0
- package/scripts/_cache_quota.py +529 -0
- package/scripts/_cache_refresh.py +163 -0
- package/scripts/_cache_validate.py +209 -0
- package/scripts/_content_root.py +42 -0
- package/scripts/_doctor_state.py +277 -0
- package/scripts/_event_detect.py +305 -0
- package/scripts/_events.py +514 -0
- package/scripts/_lifecycle_hygiene.py +568 -0
- package/scripts/_pathspec.py +91 -0
- package/scripts/_policy_show_cli.py +266 -0
- package/scripts/_precutover.py +92 -0
- package/scripts/_project_context.py +224 -0
- package/scripts/_project_definition_io.py +164 -0
- package/scripts/_relocate_snapshot.py +209 -0
- package/scripts/_relocate_states.py +343 -0
- package/scripts/_resolve_preflight_path.py +152 -0
- package/scripts/_safe_subprocess.py +167 -0
- package/scripts/_session_start_hook.py +205 -0
- package/scripts/_sor_gate_diff.py +365 -0
- package/scripts/_stdio_utf8.py +59 -0
- package/scripts/_triage_bootstrap_gitignore.py +904 -0
- package/scripts/_triage_classify_cli.py +122 -0
- package/scripts/_triage_queue_cli.py +625 -0
- package/scripts/_triage_scope_cli.py +343 -0
- package/scripts/_triage_scope_drift_cli.py +121 -0
- package/scripts/_triage_scope_ignores.py +286 -0
- package/scripts/_triage_scope_milestone.py +432 -0
- package/scripts/_triage_scope_mutations.py +337 -0
- package/scripts/_triage_scope_renderers.py +207 -0
- package/scripts/_triage_smoketest_stages.py +674 -0
- package/scripts/_triage_subscribe_cli.py +140 -0
- package/scripts/_triage_welcome_cli.py +421 -0
- package/scripts/_vbrief_build.py +239 -0
- package/scripts/_vbrief_fidelity.py +479 -0
- package/scripts/_vbrief_legacy.py +589 -0
- package/scripts/_vbrief_reconciliation.py +883 -0
- package/scripts/_vbrief_routing.py +277 -0
- package/scripts/_vbrief_safety.py +778 -0
- package/scripts/_vbrief_sources.py +312 -0
- package/scripts/_vbrief_speckit.py +262 -0
- package/scripts/_vbrief_story_quality.py +353 -0
- package/scripts/_vbrief_validation.py +299 -0
- package/scripts/build_dist.py +412 -0
- package/scripts/cache.py +1078 -0
- package/scripts/cache_scanner.py +745 -0
- package/scripts/candidates_log.py +432 -0
- package/scripts/capacity_backfill.py +680 -0
- package/scripts/capacity_show.py +653 -0
- package/scripts/ci_local.py +689 -0
- package/scripts/code_structure_validate.py +765 -0
- package/scripts/codebase_default_extractor.py +495 -0
- package/scripts/codebase_map.py +304 -0
- package/scripts/codebase_map_fresh.py +104 -0
- package/scripts/codebase_projection_registry.py +94 -0
- package/scripts/codebase_provider.py +582 -0
- package/scripts/doctor.py +2257 -0
- package/scripts/framework_commands.py +505 -0
- package/scripts/gh_rest.py +882 -0
- package/scripts/github_auth_modes.py +437 -0
- package/scripts/github_body.py +292 -0
- package/scripts/ip_risk.py +531 -0
- package/scripts/issue_emit.py +670 -0
- package/scripts/issue_ingest.py +1064 -0
- package/scripts/migrate_preflight.py +418 -0
- package/scripts/migrate_vbrief.py +2677 -0
- package/scripts/monitor_pr.py +401 -0
- package/scripts/pack_migrate_lessons.py +336 -0
- package/scripts/pack_migrate_patterns.py +254 -0
- package/scripts/pack_migrate_rules.py +350 -0
- package/scripts/pack_migrate_skills.py +423 -0
- package/scripts/pack_migrate_strategies.py +311 -0
- package/scripts/pack_migrate_swarm_spec.py +250 -0
- package/scripts/pack_render.py +434 -0
- package/scripts/packs_slice.py +712 -0
- package/scripts/platform_capabilities.py +336 -0
- package/scripts/policy.py +2826 -0
- package/scripts/policy_set.py +324 -0
- package/scripts/pr_check_closing_keywords.py +524 -0
- package/scripts/pr_check_protected_issues.py +267 -0
- package/scripts/pr_merge_readiness.py +1004 -0
- package/scripts/pr_wait_mergeable.py +669 -0
- package/scripts/prd_render.py +159 -0
- package/scripts/preflight_architecture_sor.py +974 -0
- package/scripts/preflight_branch.py +289 -0
- package/scripts/preflight_cache.py +974 -0
- package/scripts/preflight_gh.py +721 -0
- package/scripts/preflight_implementation.py +272 -0
- package/scripts/preflight_story_start.py +838 -0
- package/scripts/preflight_wip_cap.py +149 -0
- package/scripts/probe_session.py +545 -0
- package/scripts/project_render.py +293 -0
- package/scripts/quarantine_ext.py +237 -0
- package/scripts/reconcile_issues.py +1442 -0
- package/scripts/refresh-path.ps1 +107 -0
- package/scripts/release.py +2030 -0
- package/scripts/release_e2e.py +1011 -0
- package/scripts/release_publish.py +486 -0
- package/scripts/release_rollback.py +980 -0
- package/scripts/relocate.py +1034 -0
- package/scripts/resolve_changelog_unreleased.py +667 -0
- package/scripts/resolve_version.py +490 -0
- package/scripts/resume_conditions.py +706 -0
- package/scripts/ritual_sentinel.py +609 -0
- package/scripts/roadmap_render.py +635 -0
- package/scripts/rule_ownership_lint.py +325 -0
- package/scripts/scm.py +591 -0
- package/scripts/scope_audit_log.py +387 -0
- package/scripts/scope_decompose.py +654 -0
- package/scripts/scope_demote.py +509 -0
- package/scripts/scope_lifecycle.py +1126 -0
- package/scripts/scope_undo.py +772 -0
- package/scripts/session_start.py +406 -0
- package/scripts/setup_ghx.py +339 -0
- package/scripts/setup_windows.ps1 +220 -0
- package/scripts/slice_audit.py +585 -0
- package/scripts/slice_record.py +530 -0
- package/scripts/slice_record_existing.py +692 -0
- package/scripts/slug_normalize.py +178 -0
- package/scripts/spec_render.py +477 -0
- package/scripts/spec_validate.py +238 -0
- package/scripts/subagent_monitor.py +658 -0
- package/scripts/swarm_complete_cohort.py +644 -0
- package/scripts/swarm_launch.py +1206 -0
- package/scripts/swarm_readiness.py +554 -0
- package/scripts/swarm_verify_review_clean.py +438 -0
- package/scripts/swarm_worktrees.py +497 -0
- package/scripts/toolchain-check.py +52 -0
- package/scripts/triage_actions.py +871 -0
- package/scripts/triage_bootstrap.py +1153 -0
- package/scripts/triage_bulk.py +630 -0
- package/scripts/triage_classify.py +932 -0
- package/scripts/triage_help.py +1685 -0
- package/scripts/triage_queue.py +1944 -0
- package/scripts/triage_reconcile.py +581 -0
- package/scripts/triage_refresh.py +643 -0
- package/scripts/triage_scope.py +999 -0
- package/scripts/triage_scope_drift.py +575 -0
- package/scripts/triage_smoketest.py +396 -0
- package/scripts/triage_subscribe.py +399 -0
- package/scripts/triage_summary.py +1011 -0
- package/scripts/triage_welcome.py +1178 -0
- package/scripts/ts_check_lane.py +86 -0
- package/scripts/validate-links.py +64 -0
- package/scripts/validate_strategy_output.py +212 -0
- package/scripts/vbrief_activate.py +228 -0
- package/scripts/vbrief_migrate_conformance.py +368 -0
- package/scripts/vbrief_reconcile_graph.py +306 -0
- package/scripts/vbrief_reconcile_labels.py +460 -0
- package/scripts/vbrief_reconcile_umbrellas.py +741 -0
- package/scripts/vbrief_validate.py +1195 -0
- package/scripts/verify-stubs.py +61 -0
- package/scripts/verify_capacity.py +160 -0
- package/scripts/verify_encoding.py +699 -0
- package/scripts/verify_hooks_installed.py +206 -0
- package/scripts/verify_investigation.py +360 -0
- package/scripts/verify_judgment_gates.py +827 -0
- package/scripts/verify_no_task_runtime.py +171 -0
- package/scripts/verify_scm_boundary.py +509 -0
- package/scripts/verify_session_ritual.py +389 -0
- package/scripts/verify_tools.py +426 -0
- package/scripts/verify_vbrief_conformance.py +478 -0
- package/skills/deft-directive-swarm/SKILL.md +7 -26
- package/skills/deft-directive-sync/SKILL.md +1 -1
- package/tasks/architecture.yml +13 -0
- package/tasks/cache.yml +69 -0
- package/tasks/capacity.yml +38 -0
- package/tasks/change.yml +46 -0
- package/tasks/changelog.yml +24 -0
- package/tasks/ci.yml +49 -0
- package/tasks/codebase.yml +47 -0
- package/tasks/commit.yml +30 -0
- package/tasks/core.yml +126 -0
- package/tasks/deployments.yml +54 -0
- package/tasks/framework.yml +74 -0
- package/tasks/install.yml +60 -0
- package/tasks/issue.yml +50 -0
- package/tasks/migrate.yml +73 -0
- package/tasks/packs.yml +92 -0
- package/tasks/policy.yml +75 -0
- package/tasks/pr.yml +89 -0
- package/tasks/prd.yml +39 -0
- package/tasks/project.yml +27 -0
- package/tasks/reconcile.yml +32 -0
- package/tasks/relocate.yml +56 -0
- package/tasks/roadmap.yml +28 -0
- package/tasks/scm.yml +126 -0
- package/tasks/scope-undo.yml +36 -0
- package/tasks/scope.yml +141 -0
- package/tasks/session.yml +19 -0
- package/tasks/setup.yml +37 -0
- package/tasks/slice.yml +69 -0
- package/tasks/spec.yml +41 -0
- package/tasks/swarm.yml +85 -0
- package/tasks/toolchain.yml +13 -0
- package/tasks/triage-actions.yml +94 -0
- package/tasks/triage-bootstrap.yml +43 -0
- package/tasks/triage-bulk.yml +75 -0
- package/tasks/triage-classify.yml +30 -0
- package/tasks/triage-queue.yml +50 -0
- package/tasks/triage-reconcile.yml +29 -0
- package/tasks/triage-scope-drift.yml +29 -0
- package/tasks/triage-scope.yml +31 -0
- package/tasks/triage-smoketest.yml +33 -0
- package/tasks/triage-subscribe.yml +36 -0
- package/tasks/triage-summary.yml +29 -0
- package/tasks/triage-welcome.yml +32 -0
- package/tasks/ts.yml +328 -0
- package/tasks/vbrief.yml +206 -0
- package/tasks/verify.yml +292 -0
- package/templates/agents-entry.md +2 -2
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""platform_capabilities.py -- read-only worker runtime capability probe (#1557a).
|
|
3
|
+
|
|
4
|
+
Classifies the execution envelope a swarm worker will actually run in:
|
|
5
|
+
|
|
6
|
+
- ``local-unsandboxed`` -- interactive local shell without Cursor native sandbox.
|
|
7
|
+
- ``cursor-native-sandbox`` -- Cursor native sandbox with UID remap to the host user.
|
|
8
|
+
- ``cloud-headless`` -- cloud or headless agent runtimes without local host context.
|
|
9
|
+
|
|
10
|
+
The probe is intentionally read-only: it inspects environment variables,
|
|
11
|
+
``/proc/self/uid_map`` (when available), process identity, and basic cwd
|
|
12
|
+
ownership facts. It does not mutate credentials, shell out to ``gh``, or
|
|
13
|
+
touch vBRIEF lifecycle files.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import contextlib
|
|
20
|
+
import dataclasses
|
|
21
|
+
import getpass
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
from collections.abc import Mapping, Sequence
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
31
|
+
|
|
32
|
+
from _stdio_utf8 import reconfigure_stdio # noqa: E402
|
|
33
|
+
|
|
34
|
+
reconfigure_stdio()
|
|
35
|
+
|
|
36
|
+
RUNTIME_MODE_LOCAL_UNSANDBOXED = "local-unsandboxed"
|
|
37
|
+
RUNTIME_MODE_CURSOR_NATIVE_SANDBOX = "cursor-native-sandbox"
|
|
38
|
+
RUNTIME_MODE_CLOUD_HEADLESS = "cloud-headless"
|
|
39
|
+
|
|
40
|
+
KNOWN_RUNTIME_MODES: frozenset[str] = frozenset(
|
|
41
|
+
{
|
|
42
|
+
RUNTIME_MODE_LOCAL_UNSANDBOXED,
|
|
43
|
+
RUNTIME_MODE_CURSOR_NATIVE_SANDBOX,
|
|
44
|
+
RUNTIME_MODE_CLOUD_HEADLESS,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
IDENTITY_REAL_ROOT = "real-root"
|
|
49
|
+
IDENTITY_SANDBOX_REMAPPED_LOCAL_USER = "sandbox-remapped-local-user"
|
|
50
|
+
IDENTITY_LOCAL_USER = "local-user"
|
|
51
|
+
IDENTITY_UNKNOWN = "unknown"
|
|
52
|
+
|
|
53
|
+
_TRUTHY = frozenset({"1", "true", "yes", "on"})
|
|
54
|
+
|
|
55
|
+
# Env vars surfaced in the report (values only; never treated as secrets here).
|
|
56
|
+
_CURSOR_SIGNAL_VARS = (
|
|
57
|
+
"CURSOR_SANDBOX",
|
|
58
|
+
"CURSOR_SANDBOX_LANDLOCK_STATUS",
|
|
59
|
+
"CURSOR_ORIG_UID",
|
|
60
|
+
"CURSOR_ORIG_GID",
|
|
61
|
+
"CURSOR_AGENT",
|
|
62
|
+
"CURSOR_COMPOSER",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
_CLOUD_SIGNAL_VARS = (
|
|
66
|
+
"CURSOR_AGENT",
|
|
67
|
+
"GROK_BUILD",
|
|
68
|
+
"DEFT_AGENT_RUNTIME",
|
|
69
|
+
"CI",
|
|
70
|
+
"GITHUB_ACTIONS",
|
|
71
|
+
"BUILDKITE",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class UidMapEntry:
|
|
77
|
+
"""One row from ``/proc/self/uid_map``."""
|
|
78
|
+
|
|
79
|
+
inside_id: int
|
|
80
|
+
outside_id: int
|
|
81
|
+
length: int
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class OwnershipFacts:
|
|
86
|
+
"""Basic ownership interpretation for a path (typically cwd)."""
|
|
87
|
+
|
|
88
|
+
path: str
|
|
89
|
+
uid: int
|
|
90
|
+
gid: int
|
|
91
|
+
interpreted_as_sandbox_view: bool
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class RuntimeCapabilityReport:
|
|
96
|
+
"""Structured runtime capability probe output."""
|
|
97
|
+
|
|
98
|
+
runtime_mode: str
|
|
99
|
+
identity_kind: str
|
|
100
|
+
effective_uid: int | None
|
|
101
|
+
effective_username: str | None
|
|
102
|
+
uid_map: tuple[UidMapEntry, ...]
|
|
103
|
+
cursor_orig_uid: int | None
|
|
104
|
+
cursor_orig_gid: int | None
|
|
105
|
+
sandbox_uid_remap: bool
|
|
106
|
+
ownership: OwnershipFacts | None
|
|
107
|
+
signals: dict[str, str]
|
|
108
|
+
|
|
109
|
+
def to_dict(self) -> dict[str, Any]:
|
|
110
|
+
payload = dataclasses.asdict(self)
|
|
111
|
+
if self.ownership is not None:
|
|
112
|
+
payload["ownership"] = dataclasses.asdict(self.ownership)
|
|
113
|
+
payload["uid_map"] = [dataclasses.asdict(entry) for entry in self.uid_map]
|
|
114
|
+
return payload
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _env_truthy(environ: Mapping[str, str], name: str) -> bool:
|
|
118
|
+
return environ.get(name, "").strip().lower() in _TRUTHY
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _parse_int(value: str | None) -> int | None:
|
|
122
|
+
if value is None:
|
|
123
|
+
return None
|
|
124
|
+
text = value.strip()
|
|
125
|
+
if not text:
|
|
126
|
+
return None
|
|
127
|
+
try:
|
|
128
|
+
return int(text)
|
|
129
|
+
except ValueError:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def read_uid_map(path: Path) -> tuple[UidMapEntry, ...]:
|
|
134
|
+
"""Parse a uid_map file (typically ``/proc/self/uid_map``)."""
|
|
135
|
+
if not path.is_file():
|
|
136
|
+
return ()
|
|
137
|
+
entries: list[UidMapEntry] = []
|
|
138
|
+
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
139
|
+
line = raw_line.strip()
|
|
140
|
+
if not line or line.startswith("#"):
|
|
141
|
+
continue
|
|
142
|
+
parts = line.split()
|
|
143
|
+
if len(parts) != 3:
|
|
144
|
+
continue
|
|
145
|
+
try:
|
|
146
|
+
inside_id, outside_id, length = (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
147
|
+
except ValueError:
|
|
148
|
+
continue
|
|
149
|
+
entries.append(
|
|
150
|
+
UidMapEntry(inside_id=inside_id, outside_id=outside_id, length=length)
|
|
151
|
+
)
|
|
152
|
+
return tuple(entries)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def detect_sandbox_uid_remap(
|
|
156
|
+
uid_map: Sequence[UidMapEntry],
|
|
157
|
+
*,
|
|
158
|
+
effective_uid: int | None,
|
|
159
|
+
cursor_orig_uid: int | None,
|
|
160
|
+
) -> bool:
|
|
161
|
+
"""True when sandbox UID 0 is remapped to the host user, not real root."""
|
|
162
|
+
if effective_uid != 0:
|
|
163
|
+
return False
|
|
164
|
+
if cursor_orig_uid is None:
|
|
165
|
+
return False
|
|
166
|
+
return any(
|
|
167
|
+
entry.inside_id == 0 and entry.outside_id == cursor_orig_uid
|
|
168
|
+
for entry in uid_map
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def classify_identity_kind(
|
|
173
|
+
*,
|
|
174
|
+
effective_uid: int | None,
|
|
175
|
+
sandbox_uid_remap: bool,
|
|
176
|
+
) -> str:
|
|
177
|
+
if effective_uid is None:
|
|
178
|
+
return IDENTITY_UNKNOWN
|
|
179
|
+
if effective_uid == 0:
|
|
180
|
+
if sandbox_uid_remap:
|
|
181
|
+
return IDENTITY_SANDBOX_REMAPPED_LOCAL_USER
|
|
182
|
+
return IDENTITY_REAL_ROOT
|
|
183
|
+
return IDENTITY_LOCAL_USER
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _is_cloud_headless(environ: Mapping[str, str]) -> bool:
|
|
187
|
+
if _env_truthy(environ, "CURSOR_AGENT"):
|
|
188
|
+
return True
|
|
189
|
+
if _env_truthy(environ, "GROK_BUILD"):
|
|
190
|
+
return True
|
|
191
|
+
runtime = environ.get("DEFT_AGENT_RUNTIME", "").strip().lower()
|
|
192
|
+
if runtime in {"grok-build", "cloud", "headless"}:
|
|
193
|
+
return True
|
|
194
|
+
if _env_truthy(environ, "GITHUB_ACTIONS") or _env_truthy(environ, "BUILDKITE"):
|
|
195
|
+
return True
|
|
196
|
+
return _env_truthy(environ, "CI") and not _env_truthy(environ, "CURSOR_COMPOSER")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _is_cursor_native_sandbox(
|
|
200
|
+
environ: Mapping[str, str],
|
|
201
|
+
*,
|
|
202
|
+
sandbox_uid_remap: bool,
|
|
203
|
+
) -> bool:
|
|
204
|
+
if sandbox_uid_remap:
|
|
205
|
+
return True
|
|
206
|
+
if _env_truthy(environ, "CURSOR_SANDBOX"):
|
|
207
|
+
return True
|
|
208
|
+
return bool(environ.get("CURSOR_SANDBOX_LANDLOCK_STATUS", "").strip())
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def classify_runtime_mode(
|
|
212
|
+
environ: Mapping[str, str],
|
|
213
|
+
*,
|
|
214
|
+
sandbox_uid_remap: bool,
|
|
215
|
+
) -> str:
|
|
216
|
+
if _is_cloud_headless(environ):
|
|
217
|
+
return RUNTIME_MODE_CLOUD_HEADLESS
|
|
218
|
+
if _is_cursor_native_sandbox(environ, sandbox_uid_remap=sandbox_uid_remap):
|
|
219
|
+
return RUNTIME_MODE_CURSOR_NATIVE_SANDBOX
|
|
220
|
+
return RUNTIME_MODE_LOCAL_UNSANDBOXED
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _read_ownership(path: Path, *, sandbox_uid_remap: bool) -> OwnershipFacts | None:
|
|
224
|
+
try:
|
|
225
|
+
stat_result = path.stat()
|
|
226
|
+
except OSError:
|
|
227
|
+
return None
|
|
228
|
+
return OwnershipFacts(
|
|
229
|
+
path=str(path),
|
|
230
|
+
uid=stat_result.st_uid,
|
|
231
|
+
gid=stat_result.st_gid,
|
|
232
|
+
interpreted_as_sandbox_view=sandbox_uid_remap,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _collect_signals(environ: Mapping[str, str]) -> dict[str, str]:
|
|
237
|
+
names = sorted(set(_CURSOR_SIGNAL_VARS + _CLOUD_SIGNAL_VARS))
|
|
238
|
+
return {name: environ[name] for name in names if name in environ}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def probe_runtime_capabilities(
|
|
242
|
+
*,
|
|
243
|
+
environ: Mapping[str, str] | None = None,
|
|
244
|
+
uid_map_path: Path | str | None = None,
|
|
245
|
+
cwd: Path | str | None = None,
|
|
246
|
+
effective_uid_override: int | None = None,
|
|
247
|
+
) -> RuntimeCapabilityReport:
|
|
248
|
+
"""Probe the current (or injected) worker runtime envelope."""
|
|
249
|
+
env = dict(os.environ if environ is None else environ)
|
|
250
|
+
|
|
251
|
+
effective_uid: int | None
|
|
252
|
+
if effective_uid_override is not None:
|
|
253
|
+
effective_uid = effective_uid_override
|
|
254
|
+
elif hasattr(os, "getuid"):
|
|
255
|
+
effective_uid = os.getuid()
|
|
256
|
+
else:
|
|
257
|
+
effective_uid = None
|
|
258
|
+
|
|
259
|
+
effective_username = env.get("USER") or env.get("USERNAME")
|
|
260
|
+
if not effective_username:
|
|
261
|
+
with contextlib.suppress(Exception):
|
|
262
|
+
effective_username = getpass.getuser()
|
|
263
|
+
|
|
264
|
+
cursor_orig_uid = _parse_int(env.get("CURSOR_ORIG_UID"))
|
|
265
|
+
cursor_orig_gid = _parse_int(env.get("CURSOR_ORIG_GID"))
|
|
266
|
+
|
|
267
|
+
uid_map_file = (
|
|
268
|
+
Path(uid_map_path)
|
|
269
|
+
if uid_map_path is not None
|
|
270
|
+
else Path("/proc/self/uid_map")
|
|
271
|
+
)
|
|
272
|
+
uid_map = read_uid_map(uid_map_file)
|
|
273
|
+
|
|
274
|
+
sandbox_uid_remap = detect_sandbox_uid_remap(
|
|
275
|
+
uid_map,
|
|
276
|
+
effective_uid=effective_uid,
|
|
277
|
+
cursor_orig_uid=cursor_orig_uid,
|
|
278
|
+
)
|
|
279
|
+
identity_kind = classify_identity_kind(
|
|
280
|
+
effective_uid=effective_uid,
|
|
281
|
+
sandbox_uid_remap=sandbox_uid_remap,
|
|
282
|
+
)
|
|
283
|
+
runtime_mode = classify_runtime_mode(
|
|
284
|
+
env,
|
|
285
|
+
sandbox_uid_remap=sandbox_uid_remap,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
cwd_path = Path(cwd) if cwd is not None else Path.cwd()
|
|
289
|
+
ownership = _read_ownership(cwd_path, sandbox_uid_remap=sandbox_uid_remap)
|
|
290
|
+
|
|
291
|
+
return RuntimeCapabilityReport(
|
|
292
|
+
runtime_mode=runtime_mode,
|
|
293
|
+
identity_kind=identity_kind,
|
|
294
|
+
effective_uid=effective_uid,
|
|
295
|
+
effective_username=effective_username,
|
|
296
|
+
uid_map=uid_map,
|
|
297
|
+
cursor_orig_uid=cursor_orig_uid,
|
|
298
|
+
cursor_orig_gid=cursor_orig_gid,
|
|
299
|
+
sandbox_uid_remap=sandbox_uid_remap,
|
|
300
|
+
ownership=ownership,
|
|
301
|
+
signals=_collect_signals(env),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def get_platform_capabilities() -> RuntimeCapabilityReport:
|
|
306
|
+
"""Return runtime capabilities for the live worker environment."""
|
|
307
|
+
return probe_runtime_capabilities()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
311
|
+
parser = argparse.ArgumentParser(
|
|
312
|
+
description="Read-only worker runtime capability probe (#1557a)."
|
|
313
|
+
)
|
|
314
|
+
parser.add_argument(
|
|
315
|
+
"--json",
|
|
316
|
+
action="store_true",
|
|
317
|
+
help="Emit structured JSON on stdout.",
|
|
318
|
+
)
|
|
319
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
320
|
+
|
|
321
|
+
report = get_platform_capabilities()
|
|
322
|
+
if args.json:
|
|
323
|
+
print(json.dumps(report.to_dict(), indent=2, sort_keys=True))
|
|
324
|
+
else:
|
|
325
|
+
print(f"runtime_mode={report.runtime_mode}")
|
|
326
|
+
print(f"identity_kind={report.identity_kind}")
|
|
327
|
+
if report.sandbox_uid_remap:
|
|
328
|
+
print(
|
|
329
|
+
"sandbox_uid_remap=true "
|
|
330
|
+
f"(host_uid={report.cursor_orig_uid})"
|
|
331
|
+
)
|
|
332
|
+
return 0
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
raise SystemExit(main())
|