@namewta/speculo 1.0.5 → 1.0.7

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 (151) hide show
  1. package/README.md +1 -1
  2. package/dist/src/kernel.d.ts +23 -2
  3. package/dist/src/kernel.js +9 -0
  4. package/dist/src/kernel.js.map +1 -1
  5. package/dist/src/ops-resources.d.ts +3 -0
  6. package/dist/src/ops-resources.js +188 -0
  7. package/dist/src/ops-resources.js.map +1 -0
  8. package/dist/src/refresh.js +20 -2
  9. package/dist/src/refresh.js.map +1 -1
  10. package/dist/src/structured.js +7 -0
  11. package/dist/src/structured.js.map +1 -1
  12. package/dist/src/workflows.js +1 -1
  13. package/dist/src/workflows.js.map +1 -1
  14. package/package.json +1 -1
  15. package/template/.speculo/README.md +3 -3
  16. package/template/.speculo/kernel/README.md +3 -0
  17. package/template/.speculo/kernel/checkpoint.schema.json +138 -4
  18. package/template/commands/archive-and-consolidate.md +5 -1
  19. package/template/commands/status.md +2 -2
  20. package/template/workflows/ops/D-project-deploy/D-project-deploy.md +34 -0
  21. package/template/workflows/ops/H-host-manage/H-host-manage.md +30 -0
  22. package/template/workflows/ops/I-initialize/I-initialize.md +28 -0
  23. package/template/workflows/ops/INDEX.md +15 -17
  24. package/template/workflows/ops/README.md +59 -93
  25. package/template/workflows/ops/_state/status.json +15 -3
  26. package/template/workflows/ops/common/CAPABILITIES.md +23 -0
  27. package/template/workflows/ops/common/USAGE.md +112 -0
  28. package/template/workflows/ops/common/examples/README.md +7 -0
  29. package/template/workflows/ops/common/examples/binding.example.json +12 -0
  30. package/template/workflows/ops/common/examples/compose-app.example.json +56 -0
  31. package/template/workflows/ops/common/examples/credential.example.json +9 -0
  32. package/template/workflows/ops/common/examples/environment-request.example.json +7 -0
  33. package/template/workflows/ops/common/examples/register.example.json +44 -0
  34. package/template/workflows/ops/common/examples/shared-allocation.example.json +37 -0
  35. package/template/workflows/ops/common/rules/activation-and-memory.md +5 -20
  36. package/template/workflows/ops/common/rules/persistence-and-secrets.md +27 -0
  37. package/template/workflows/ops/common/rules/recovery.md +15 -0
  38. package/template/workflows/ops/common/rules/shared-services.md +13 -0
  39. package/template/workflows/ops/common/schemas/allocation.schema.json +92 -0
  40. package/template/workflows/ops/common/schemas/approval.schema.json +41 -21
  41. package/template/workflows/ops/common/schemas/binding.schema.json +73 -0
  42. package/template/workflows/ops/common/schemas/deployment.schema.json +244 -0
  43. package/template/workflows/ops/common/schemas/host.schema.json +88 -0
  44. package/template/workflows/ops/common/schemas/plan.schema.json +1573 -0
  45. package/template/workflows/ops/common/schemas/project.schema.json +55 -11
  46. package/template/workflows/ops/common/schemas/spec.schema.json +1032 -0
  47. package/template/workflows/ops/common/schemas/status.schema.json +758 -17
  48. package/template/workflows/ops/common/service-profiles/custom.md +5 -0
  49. package/template/workflows/ops/common/service-profiles/docker-engine.md +11 -0
  50. package/template/workflows/ops/common/service-profiles/minio.md +7 -0
  51. package/template/workflows/ops/common/service-profiles/mysql.md +7 -0
  52. package/template/workflows/ops/common/service-profiles/redis.md +7 -0
  53. package/template/workflows/ops/common/templates/CONTROLLER-RECORD.md +13 -0
  54. package/template/workflows/ops/common/templates/EXECUTION-PLAN.md +15 -0
  55. package/template/workflows/ops/common/templates/HOST-README.md +11 -0
  56. package/template/workflows/ops/common/templates/PROJECT-README.md +15 -0
  57. package/template/workflows/ops/common/tests/test_ops.mjs +752 -0
  58. package/template/workflows/ops/common/tools/bootstrap.ps1 +18 -0
  59. package/template/workflows/ops/common/tools/bootstrap.sh +27 -0
  60. package/template/workflows/ops/common/tools/demo-local.mjs +101 -0
  61. package/template/workflows/ops/common/tools/ops.mjs +4 -0
  62. package/template/workflows/ops/common/tools/opslib/agent.mjs +873 -0
  63. package/template/workflows/ops/common/tools/opslib/cli.mjs +311 -0
  64. package/template/workflows/ops/common/tools/opslib/core.mjs +393 -0
  65. package/template/workflows/ops/common/tools/opslib/docs.mjs +252 -0
  66. package/template/workflows/ops/common/tools/opslib/execution.mjs +378 -0
  67. package/template/workflows/ops/common/tools/opslib/host_recipes.mjs +109 -0
  68. package/template/workflows/ops/common/tools/opslib/model.mjs +272 -0
  69. package/template/workflows/ops/common/tools/opslib/native_windows.mjs +36 -0
  70. package/template/workflows/ops/common/tools/opslib/planner.mjs +687 -0
  71. package/template/workflows/ops/common/tools/opslib/services.mjs +76 -0
  72. package/template/workflows/ops/common/tools/opslib/sources.mjs +56 -0
  73. package/template/workflows/ops/common/tools/opslib/transport.mjs +127 -0
  74. package/template/workflows/ops/common/tools/validate-ops.mjs +43 -1012
  75. package/template/workflows/ops/manifest.json +60 -1
  76. package/template/workflows/ops/runtime-contract.json +1 -6
  77. package/template/workflows/ops/A-archive-and-learn/A-archive-and-learn.md +0 -73
  78. package/template/workflows/ops/A-archive-and-learn/promotion-plan-template.md +0 -41
  79. package/template/workflows/ops/A-archive-and-learn/retrospective-template.md +0 -53
  80. package/template/workflows/ops/E-execute-and-stabilize/E-execute-and-stabilize.md +0 -82
  81. package/template/workflows/ops/E-execute-and-stabilize/attempt-summary-template.md +0 -44
  82. package/template/workflows/ops/E-execute-and-stabilize/diagnosis-template.md +0 -24
  83. package/template/workflows/ops/E-execute-and-stabilize/handoff-template.md +0 -32
  84. package/template/workflows/ops/E-execute-and-stabilize/rollback-template.md +0 -26
  85. package/template/workflows/ops/E-execute-and-stabilize/verification-state-template.json +0 -30
  86. package/template/workflows/ops/E-execute-and-stabilize/verification-template.md +0 -55
  87. package/template/workflows/ops/H-computer-hygiene/H-computer-hygiene.md +0 -105
  88. package/template/workflows/ops/H-computer-hygiene/examples/README.md +0 -9
  89. package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.environment.md +0 -45
  90. package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.json +0 -970
  91. package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.md +0 -216
  92. package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.environment.md +0 -45
  93. package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.json +0 -970
  94. package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.md +0 -216
  95. package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.environment.md +0 -45
  96. package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.json +0 -969
  97. package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.md +0 -216
  98. package/template/workflows/ops/H-computer-hygiene/references/00-sources.md +0 -58
  99. package/template/workflows/ops/H-computer-hygiene/references/01-safety.md +0 -52
  100. package/template/workflows/ops/H-computer-hygiene/references/10-windows.md +0 -47
  101. package/template/workflows/ops/H-computer-hygiene/references/11-macos.md +0 -46
  102. package/template/workflows/ops/H-computer-hygiene/references/12-linux.md +0 -13
  103. package/template/workflows/ops/H-computer-hygiene/references/20-toolchains.md +0 -83
  104. package/template/workflows/ops/H-computer-hygiene/references/21-path-migration.md +0 -64
  105. package/template/workflows/ops/H-computer-hygiene/references/22-repositories.md +0 -35
  106. package/template/workflows/ops/H-computer-hygiene/references/30-software-removal.md +0 -41
  107. package/template/workflows/ops/H-computer-hygiene/references/40-report-spec.md +0 -54
  108. package/template/workflows/ops/H-computer-hygiene/references/50-speculo-integration.md +0 -26
  109. package/template/workflows/ops/H-computer-hygiene/references/source-register.json +0 -406
  110. package/template/workflows/ops/H-computer-hygiene/rules/catalog.json +0 -191
  111. package/template/workflows/ops/H-computer-hygiene/scripts/hygiene.py +0 -1348
  112. package/template/workflows/ops/H-computer-hygiene/scripts/run.ps1 +0 -7
  113. package/template/workflows/ops/H-computer-hygiene/scripts/run.sh +0 -5
  114. package/template/workflows/ops/H-computer-hygiene/templates/config.example.json +0 -13
  115. package/template/workflows/ops/H-computer-hygiene/templates/maven-settings.fragment.xml +0 -6
  116. package/template/workflows/ops/H-computer-hygiene/templates/native-evidence.example.json +0 -19
  117. package/template/workflows/ops/H-computer-hygiene/tests/cli_smoke.py +0 -65
  118. package/template/workflows/ops/H-computer-hygiene/tests/make_examples.py +0 -114
  119. package/template/workflows/ops/H-computer-hygiene/tests/test_hygiene.py +0 -486
  120. package/template/workflows/ops/I-intake-and-assess/I-intake-and-assess.md +0 -73
  121. package/template/workflows/ops/I-intake-and-assess/change-status-template.json +0 -26
  122. package/template/workflows/ops/I-intake-and-assess/collector-catalog.md +0 -28
  123. package/template/workflows/ops/I-intake-and-assess/deployment-dossier-template.md +0 -62
  124. package/template/workflows/ops/I-intake-and-assess/global-change-status-template.json +0 -26
  125. package/template/workflows/ops/I-intake-and-assess/project-detection.md +0 -38
  126. package/template/workflows/ops/I-intake-and-assess/request-template.md +0 -44
  127. package/template/workflows/ops/I-intake-and-assess/system-report-template.md +0 -34
  128. package/template/workflows/ops/I-intake-and-assess/target-profile-template.json +0 -24
  129. package/template/workflows/ops/P-plan-and-approve/P-plan-and-approve.md +0 -72
  130. package/template/workflows/ops/P-plan-and-approve/plan-review-template.md +0 -78
  131. package/template/workflows/ops/_state/archive/.gitkeep +0 -1
  132. package/template/workflows/ops/_state/changes/.gitkeep +0 -1
  133. package/template/workflows/ops/common/rules/artifact-contract.md +0 -38
  134. package/template/workflows/ops/common/rules/closure-and-learning.md +0 -27
  135. package/template/workflows/ops/common/rules/evidence-and-redaction.md +0 -22
  136. package/template/workflows/ops/common/rules/execution-loop.md +0 -27
  137. package/template/workflows/ops/common/rules/path-and-scope-contract.md +0 -21
  138. package/template/workflows/ops/common/rules/plan-and-approval.md +0 -25
  139. package/template/workflows/ops/common/rules/project-and-change-scope.md +0 -20
  140. package/template/workflows/ops/common/rules/target-profile-and-release-gates.md +0 -44
  141. package/template/workflows/ops/common/schemas/attempt.schema.json +0 -42
  142. package/template/workflows/ops/common/schemas/change-status.schema.json +0 -43
  143. package/template/workflows/ops/common/schemas/deployment-model.schema.json +0 -26
  144. package/template/workflows/ops/common/schemas/implementation-plan.schema.json +0 -221
  145. package/template/workflows/ops/common/schemas/inventory-snapshot.schema.json +0 -31
  146. package/template/workflows/ops/common/schemas/journal-event.schema.json +0 -22
  147. package/template/workflows/ops/common/schemas/promotion-approval.schema.json +0 -20
  148. package/template/workflows/ops/common/schemas/promotion-manifest.schema.json +0 -22
  149. package/template/workflows/ops/common/schemas/target-profile.schema.json +0 -32
  150. package/template/workflows/ops/common/schemas/verification-state.schema.json +0 -30
  151. package/template/workflows/ops/common/tools/close-change.mjs +0 -177
@@ -0,0 +1,76 @@
1
+ /** Typed shared-service allocation contracts. No guessing unknown service products. */
2
+ import { identifier, OpsError } from "./core.mjs";
3
+
4
+ export function secret(ref, field) {
5
+ return "{{credential:" + ref + ":" + field + "}}";
6
+ }
7
+
8
+ export function allocationOperation(status, dep, a, p) {
9
+ const adapter = p.adapter;
10
+ const project = status.projects[dep.project_id];
11
+ if (project.service_type !== adapter) throw new OpsError("allocation adapter differs from registered provider service_type");
12
+ if (!p.admin_credential_ref || p.admin_credential_ref === a.credential_ref) {
13
+ throw new OpsError("application credential must differ from administrator credential");
14
+ }
15
+ if (!dep.credential_refs.includes(p.admin_credential_ref)) {
16
+ throw new OpsError("admin credential must be registered on the provider deployment");
17
+ }
18
+ if (!p.app_username || !/^[A-Za-z][A-Za-z0-9_-]{1,47}$/.test(p.app_username)) {
19
+ throw new OpsError("application username needs an explicit safe name");
20
+ }
21
+ const service = dep.service;
22
+ const common = {
23
+ kind: adapter + "-allocation",
24
+ provider_root: dep.root,
25
+ compose_name: dep.compose_name,
26
+ resource: a.resource_name,
27
+ app_username: p.app_username,
28
+ app_password: secret(a.credential_ref, "password"),
29
+ admin_username: secret(p.admin_credential_ref, "username"),
30
+ admin_password: secret(p.admin_credential_ref, "password"),
31
+ owner_project_id: a.owner_project_id,
32
+ environment: a.environment,
33
+ credential_ref: a.credential_ref,
34
+ };
35
+ if (adapter === "mysql" || adapter === "redis") {
36
+ if (dep.method !== "compose" || !service.compose_service) {
37
+ throw new OpsError("built-in MySQL/Redis allocator requires a managed Compose provider and explicit compose_service");
38
+ }
39
+ common.compose_service = identifier(service.compose_service);
40
+ }
41
+ if (adapter === "mysql") {
42
+ if (a.resource_kind !== "database" || !/^[a-z][a-z0-9_]{1,47}$/.test(a.resource_name)) {
43
+ throw new OpsError("MySQL requires a safe logical database name");
44
+ }
45
+ const privileges = p.privileges ?? ["SELECT", "INSERT", "UPDATE", "DELETE"];
46
+ const allowed = new Set(["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "INDEX", "REFERENCES", "DROP", "CREATE TEMPORARY TABLES", "EXECUTE"]);
47
+ if (!privileges.length || privileges.some((x) => !allowed.has(x))) {
48
+ throw new OpsError("unsupported MySQL grants; administrative/global privileges are forbidden");
49
+ }
50
+ common.privileges = privileges;
51
+ } else if (adapter === "redis") {
52
+ if (a.resource_kind !== "redis-acl" || !/^[a-zA-Z][a-zA-Z0-9:_-]+$/.test(p.prefix ?? "")) {
53
+ throw new OpsError("Redis shared allocation requires an explicit key prefix and redis-acl resource");
54
+ }
55
+ if (service.acl_persistence !== "data/redis/acl/users.acl") {
56
+ throw new OpsError("Redis provider must persist ACLs at data/redis/acl/users.acl and configure aclfile");
57
+ }
58
+ if (!["provider-wide", "application-export", "unverified"].includes(a.recovery_scope)) {
59
+ throw new OpsError("Redis prefix/ACL is not proof of independently restorable data");
60
+ }
61
+ common.prefix = p.prefix;
62
+ } else if (adapter === "minio") {
63
+ if (a.resource_kind !== "bucket" || !/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(a.resource_name)) {
64
+ throw new OpsError("MinIO allocation needs a valid bucket name");
65
+ }
66
+ if (!service.endpoint) throw new OpsError("MinIO provider requires explicit administrative endpoint");
67
+ if (p.allow_secret_argv !== true) {
68
+ throw new OpsError("mc admin user add exposes the new password briefly to privileged local process inspection; explicitly review allow_secret_argv or use an existing verified allocation");
69
+ }
70
+ if (!p.client_path) throw new OpsError("MinIO needs a reviewed installed mc client_path");
71
+ Object.assign(common, { endpoint: service.endpoint, client_path: p.client_path, secret_argv_acknowledged: true });
72
+ } else {
73
+ throw new OpsError("unknown provider adapter: use a version-verified explicit existing-resource probe, not guessed commands");
74
+ }
75
+ return common;
76
+ }
@@ -0,0 +1,56 @@
1
+ /** Explicit pinned Git acquisition into controller-owned immutable source snapshots. */
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { identifier, now, OpsError, privateDir, withLock, writeJson } from "./core.mjs";
6
+ import { load } from "./model.mjs";
7
+ import { analyze } from "./cli.mjs";
8
+
9
+ export function fetchSource(state, projectId, repo, commit, allowNetwork) {
10
+ load(state);
11
+ identifier(projectId);
12
+ let u;
13
+ try { u = new URL(repo); } catch { throw new OpsError("use an explicit credential-free HTTPS repository URL; authentication belongs in an approved Git helper"); }
14
+ if (!allowNetwork) throw new OpsError("source-fetch requires explicit --allow-network");
15
+ if (u.protocol !== "https:" || !u.hostname || u.username || u.password) {
16
+ throw new OpsError("use an explicit credential-free HTTPS repository URL; authentication belongs in an approved Git helper");
17
+ }
18
+ if (!/^[a-f0-9]{40}$/.test(commit)) throw new OpsError("Git source must be pinned by full 40-character commit SHA");
19
+ const dest = join(state, "sources", projectId, commit);
20
+ const hooks = join(state, "sources", ".empty-hooks");
21
+ privateDir(hooks);
22
+ return withLock(join(state, ".locks", "source"), { project_id: projectId, commit }, () => {
23
+ const env = {
24
+ ...process.env,
25
+ GIT_TERMINAL_PROMPT: "0",
26
+ GIT_CONFIG_NOSYSTEM: "1",
27
+ GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null",
28
+ GIT_LFS_SKIP_SMUDGE: "1",
29
+ };
30
+ const git = ["git", "-c", "core.hooksPath=" + hooks];
31
+ const run = (argv, timeout) => {
32
+ const p = spawnSync(argv[0], argv.slice(1), { env, encoding: "utf8", timeout });
33
+ if (p.error) throw p.error;
34
+ if (p.status !== 0) throw new Error(p.stderr || "git failed");
35
+ return p;
36
+ };
37
+ if (!existsSync(dest)) {
38
+ privateDir(join(state, "sources", projectId));
39
+ run([...git, "clone", "--no-checkout", "--", repo, dest], 600000);
40
+ run([...git, "-C", dest, "checkout", "--detach", commit], 300000);
41
+ }
42
+ const actual = run([...git, "-C", dest, "rev-parse", "HEAD"], 30000).stdout.trim();
43
+ if (actual !== commit) throw new OpsError("source snapshot identity mismatch; do not overwrite existing checkout");
44
+ const result = {
45
+ project_id: projectId,
46
+ repository: repo,
47
+ commit,
48
+ path: dest,
49
+ at: now(),
50
+ analysis: analyze(dest),
51
+ submodules: "not initialized; each requires independent pinned review",
52
+ };
53
+ writeJson(join(state, "sources", projectId, commit + ".source.json"), result);
54
+ return result;
55
+ });
56
+ }
@@ -0,0 +1,127 @@
1
+ /** Strict host-key SSH and subprocess local transport. No passwords in argv. */
2
+ import { spawnSync } from "node:child_process";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { canonical, digest, noSymlinks, OpsError, redact, UnknownResult } from "./core.mjs";
7
+ import { fingerprint } from "./agent.mjs";
8
+
9
+ const AGENT = join(dirname(fileURLToPath(import.meta.url)), "agent.mjs");
10
+
11
+ export const transportHooks = { spawnSync };
12
+
13
+ function shlexQuote(s) {
14
+ if (s === "") return "''";
15
+ if (/[^\w@%+=:,./-]/.test(s)) return "'" + s.replaceAll("'", "'\"'\"'") + "'";
16
+ return s;
17
+ }
18
+ function shlexJoin(args) {
19
+ return args.map(shlexQuote).join(" ");
20
+ }
21
+
22
+ export function hostTransportDigest(host) {
23
+ const c = host.connection;
24
+ let kh = null;
25
+ if (host.transport === "ssh") {
26
+ const p = c.known_hosts;
27
+ noSymlinks(p, { allowMissing: false });
28
+ kh = digest(readFileSync(p));
29
+ }
30
+ return digest({ host, known_hosts_digest: kh });
31
+ }
32
+
33
+ function injectRequest(source, request) {
34
+ const b64 = Buffer.from(canonical(request)).toString("base64");
35
+ const assign = `globalThis.OPS_REQUEST = JSON.parse(Buffer.from(${JSON.stringify(b64)}, "base64").toString("utf8"));`;
36
+ const lines = source.split(/\r?\n/);
37
+ let lastImport = -1;
38
+ for (let i = 0; i < lines.length; i++) {
39
+ if (/^\s*import\s/.test(lines[i])) lastImport = i;
40
+ }
41
+ lines.splice(lastImport + 1, 0, assign);
42
+ return lines.join("\n");
43
+ }
44
+
45
+ export function call(host, request, { timeout = 1800 } = {}) {
46
+ request = { identity: host.identity, ...request };
47
+ const content = injectRequest(readFileSync(AGENT, "utf8"), request);
48
+ if (host.transport === "local") {
49
+ const argv = [process.execPath, "--input-type=module"];
50
+ let p;
51
+ try {
52
+ p = transportHooks.spawnSync(argv[0], argv.slice(1), {
53
+ input: content,
54
+ encoding: "utf8",
55
+ timeout: timeout * 1000,
56
+ maxBuffer: 64 * 1024 * 1024,
57
+ windowsHide: true,
58
+ });
59
+ } catch (e) {
60
+ if (["step", "lock", "unlock"].includes(request.action)) throw new UnknownResult("target transport interrupted; inspect receipts before retry");
61
+ throw new OpsError("target read unavailable: " + e);
62
+ }
63
+ return decode(p, request);
64
+ }
65
+ const c = host.connection;
66
+ const kh = resolve(c.known_hosts);
67
+ noSymlinks(kh, { allowMissing: false });
68
+ const argv = ["ssh", "-T", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", "-o", `UserKnownHostsFile=${kh}`,
69
+ "-o", "ConnectTimeout=15", "-o", "ServerAliveInterval=15", "-o", "ServerAliveCountMax=3", "-p", String(c.port ?? 22)];
70
+ if (c.identity_file) argv.push("-i", c.identity_file, "-o", "IdentitiesOnly=yes");
71
+ let remote = [c.node, "--input-type=module"];
72
+ if (c.sudo) remote = ["sudo", "-n", "--", ...remote];
73
+ let remoteCommand;
74
+ if ((c.shell ?? "posix") === "powershell") {
75
+ if (c.sudo) throw new OpsError("sudo not valid for PowerShell transport");
76
+ const inner = "& " + remote.map((x) => "'" + x.replaceAll("'", "''") + "'").join(" ");
77
+ const ps = Buffer.from(inner, "utf16le").toString("base64");
78
+ remoteCommand = "powershell -NoProfile -NonInteractive -EncodedCommand " + ps;
79
+ } else {
80
+ remoteCommand = shlexJoin(remote);
81
+ }
82
+ argv.push("--", c.username + "@" + c.hostname, remoteCommand);
83
+ let p;
84
+ try {
85
+ p = transportHooks.spawnSync(argv[0], argv.slice(1), {
86
+ input: content,
87
+ encoding: "buffer",
88
+ timeout: timeout * 1000,
89
+ maxBuffer: 64 * 1024 * 1024,
90
+ windowsHide: true,
91
+ });
92
+ } catch (e) {
93
+ if (["step", "lock", "unlock"].includes(request.action)) throw new UnknownResult("target transport interrupted; inspect receipts before retry");
94
+ throw new OpsError("target read unavailable: " + e);
95
+ }
96
+ return decode(p, request);
97
+ }
98
+
99
+ function decode(p, request) {
100
+ if (p.error && (p.error.code === "ETIMEDOUT" || p.signal === "SIGTERM")) {
101
+ if (["step", "lock", "unlock"].includes(request.action)) throw new UnknownResult("target transport interrupted; inspect receipts before retry");
102
+ throw new OpsError("target read unavailable: " + p.error);
103
+ }
104
+ const stdout = Buffer.isBuffer(p.stdout) ? p.stdout.toString("utf8") : (p.stdout || "");
105
+ const stderr = Buffer.isBuffer(p.stderr) ? p.stderr.toString("utf8") : (p.stderr || "");
106
+ if (p.status !== 0) {
107
+ const text = redact(stderr, request.secrets || []).slice(-2000);
108
+ if (["step", "lock", "unlock"].includes(request.action)) throw new UnknownResult("target transport failed: " + text);
109
+ throw new OpsError("target read failed: " + text);
110
+ }
111
+ let value;
112
+ try { value = JSON.parse(stdout); }
113
+ catch { throw new UnknownResult("invalid target response; stdout noise or interrupted operation"); }
114
+ if (!value.ok) throw new OpsError("target blocked: " + (value.error || "unknown"));
115
+ return value.result;
116
+ }
117
+
118
+ export function probeLocal() {
119
+ const host = {
120
+ host_id: "probe-local",
121
+ platform: process.platform === "win32" ? "windows" : "linux",
122
+ transport: "local",
123
+ connection: {},
124
+ identity: fingerprint(),
125
+ };
126
+ return call(host, { action: "probe" }, { timeout: 120 });
127
+ }