sneakoscope 8.1.2 → 8.2.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/README.md +96 -58
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +5 -51
- package/dist/cli/command-manifest-lite.js +2 -1
- package/dist/cli/command-registry.js +2 -1
- package/dist/cli/global-mode-router.js +1 -1
- package/dist/cli/install-helpers-codex-lb-config.js +112 -914
- package/dist/cli/install-helpers-codex-lb-shared.js +3 -44
- package/dist/cli/install-helpers-install-support.js +4 -20
- package/dist/cli/install-helpers.js +36 -2716
- package/dist/cli/router.js +9 -9
- package/dist/commands/bridge-command-output.js +111 -0
- package/dist/commands/bridge.js +320 -0
- package/dist/commands/codex-app.js +39 -123
- package/dist/commands/doctor-helpers.js +380 -0
- package/dist/commands/doctor-profile.js +3 -0
- package/dist/commands/doctor.js +114 -873
- package/dist/commands/mad-sks.js +0 -4
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/agent-effort-policy.js +11 -21
- package/dist/core/agents/agent-recursion-guard.js +26 -0
- package/dist/core/agents/agent-runner-codex-exec.js +7 -9
- package/dist/core/architecture-hardening/contracts/contracts.js +225 -161
- package/dist/core/architecture-hardening/state/state-service.js +49 -13
- package/dist/core/auto-review.js +15 -9
- package/dist/core/codex/codex-config-guard.js +8 -0
- package/dist/core/codex-adapter.js +8 -10
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -29
- package/dist/core/codex-app/codex-app-launcher.js +10 -12
- package/dist/core/codex-app/codex-app-restart.js +10 -12
- package/dist/core/codex-app/menubar/constants.js +2 -5
- package/dist/core/codex-app/menubar/global-install.js +3 -2
- package/dist/core/codex-app/menubar/installer/result.js +23 -0
- package/dist/core/codex-app/menubar/installer/runtime.js +28 -0
- package/dist/core/codex-app/menubar/installer.js +5 -96
- package/dist/core/codex-app/menubar/migration.js +1 -42
- package/dist/core/codex-app/openrouter-provider.js +0 -78
- package/dist/core/codex-app.js +97 -360
- package/dist/core/codex-control/codex-app-server-runtime-env.js +8 -28
- package/dist/core/codex-control/codex-current-image-path-real-probe.js +2 -3
- package/dist/core/codex-control/codex-current-web-search-probe.js +2 -3
- package/dist/core/codex-control/codex-sdk-adapter.js +5 -48
- package/dist/core/codex-control/codex-task-runner.js +16 -60
- package/dist/core/codex-control/desktop-bridge-launch-guard.js +227 -0
- package/dist/core/codex-exec-output-schema.js +11 -13
- package/dist/core/codex-lb/bridge-contracts.js +2 -0
- package/dist/core/codex-lb/bridge-runtime-validation/capability.js +104 -0
- package/dist/core/codex-lb/bridge-runtime-validation/catalog.js +52 -0
- package/dist/core/codex-lb/bridge-runtime-validation/shared.js +109 -0
- package/dist/core/codex-lb/bridge-runtime-validation/status.js +250 -0
- package/dist/core/codex-lb/bridge-runtime-validation.js +106 -0
- package/dist/core/codex-lb/capability-runner.js +505 -201
- package/dist/core/codex-lb/codex-lb-env.js +179 -12
- package/dist/core/codex-lb/combined-catalog/builder.js +86 -0
- package/dist/core/codex-lb/combined-catalog/contracts.js +5 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +170 -0
- package/dist/core/codex-lb/combined-catalog/persistence.js +207 -0
- package/dist/core/codex-lb/combined-catalog/shared.js +12 -0
- package/dist/core/codex-lb/combined-catalog/storage.js +171 -0
- package/dist/core/codex-lb/combined-catalog.js +12 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +48 -63
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +115 -83
- package/dist/core/codex-lb/desktop-bridge/launchd.js +20 -13
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/security.js +381 -267
- package/dist/core/codex-lb/desktop-bridge/server.js +148 -19
- package/dist/core/codex-lb/desktop-bridge/state.js +56 -70
- package/dist/core/codex-lb/desktop-bridge/types.js +6 -1
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +281 -106
- package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +245 -0
- package/dist/core/codex-lb/desktop-bridge-migration/historical-toml.js +56 -0
- package/dist/core/codex-lb/desktop-bridge-migration/metadata.js +73 -0
- package/dist/core/codex-lb/desktop-bridge-migration/receipt.js +52 -0
- package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +106 -0
- package/dist/core/codex-lb/desktop-bridge-migration/types.js +1 -0
- package/dist/core/codex-lb/desktop-bridge-migration.js +238 -0
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +257 -0
- package/dist/core/codex-lb/desktop-controller-v3/diagnostics.js +62 -0
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +203 -0
- package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +293 -0
- package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +144 -0
- package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +182 -0
- package/dist/core/codex-lb/desktop-controller-v3/shared.js +279 -0
- package/dist/core/codex-lb/desktop-controller-v3/status.js +269 -0
- package/dist/core/codex-lb/desktop-controller-v3/types.js +1 -0
- package/dist/core/codex-lb/desktop-controller-v3/verification.js +89 -0
- package/dist/core/codex-lb/desktop-controller-v3.js +67 -0
- package/dist/core/codex-lb/desktop-controller.js +2 -1730
- package/dist/core/codex-lb/desktop-service.js +462 -627
- package/dist/core/codex-lb/migration-receipt.js +560 -118
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +37 -53
- package/dist/core/codex-lb/probes/bridge-probe.js +125 -64
- package/dist/core/codex-lb/probes/catalog-probe.js +36 -136
- package/dist/core/codex-lb/probes/computer-use-probe.js +63 -39
- package/dist/core/codex-lb/probes/image-generation-probe.js +98 -41
- package/dist/core/codex-lb/probes/probe-evidence.js +32 -33
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +69 -43
- package/dist/core/codex-lb/provider-credentials/configuration.js +127 -0
- package/dist/core/codex-lb/provider-credentials/locks.js +11 -0
- package/dist/core/codex-lb/provider-credentials/private-snapshot.js +28 -0
- package/dist/core/codex-lb/provider-credentials/removal.js +33 -0
- package/dist/core/codex-lb/provider-credentials/resolution.js +59 -0
- package/dist/core/codex-lb/provider-credentials/runtime.js +55 -0
- package/dist/core/codex-lb/provider-credentials/types.js +1 -0
- package/dist/core/codex-lb/provider-credentials/validation-store.js +137 -0
- package/dist/core/codex-lb/provider-credentials.js +5 -0
- package/dist/core/codex-lb/provider-registry.js +323 -0
- package/dist/core/codex-lb/provider-route-policy.js +118 -0
- package/dist/core/codex-lb/request-route-resolver.js +172 -0
- package/dist/core/codex-lb/route-index.js +148 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +158 -286
- package/dist/core/commands/basic-cli.js +1 -1
- package/dist/core/commands/check-command.js +3 -10
- package/dist/core/commands/gate-result-contract.js +10 -14
- package/dist/core/commands/intent-normalization/normalizer.js +1 -1
- package/dist/core/commands/mad-sks-command.js +2 -6
- package/dist/core/commands/release-command.js +11 -1
- package/dist/core/commands/telegram-command.js +46 -6
- package/dist/core/doctor/doctor-readiness-matrix.js +9 -7
- package/dist/core/doctor/skill-legacy-surface.js +5 -5
- package/dist/core/evidence/v2/evidence-key.js +30 -3
- package/dist/core/feature-fixtures.js +8 -1
- package/dist/core/feature-registry.js +3 -2
- package/dist/core/hooks-runtime/stop-finalization.js +11 -1
- package/dist/core/hooks-runtime/subagent-context.js +15 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +73 -87
- package/dist/core/image-ux-review/imagegen-adapter.js +108 -94
- package/dist/core/image-ux-review/real-callout-extractor.js +144 -14
- package/dist/core/imagegen/desktop-bridge-imagegen-target.js +77 -0
- package/dist/core/imagegen/imagegen-capability.js +43 -151
- package/dist/core/init/skills/filesystem.js +89 -0
- package/dist/core/init/skills/inventory.js +97 -0
- package/dist/core/init/skills/manifest.js +151 -0
- package/dist/core/init/skills.js +5 -325
- package/dist/core/init.js +0 -63
- package/dist/core/install/installed-package-smoke.js +1 -1
- package/dist/core/managed-assets/managed-assets-manifest.js +0 -1
- package/dist/core/policy/latest-version-guidance.js +15 -2
- package/dist/core/preflight/parallel-preflight-engine.js +27 -51
- package/dist/core/provider/provider-badge.js +9 -4
- package/dist/core/provider/provider-context.js +57 -107
- package/dist/core/providers/openrouter/openrouter-account.js +3 -1
- package/dist/core/providers/openrouter/openrouter-secret-store.js +91 -22
- package/dist/core/release/desktop-bridge-release-evidence.js +457 -0
- package/dist/core/release/gate-manifest.js +0 -1
- package/dist/core/release/main-push-guard.js +13 -0
- package/dist/core/release/main-push-receipt.js +11 -0
- package/dist/core/release/physical-release-gates.js +664 -61
- package/dist/core/release/publish-preflight.js +3 -3
- package/dist/core/release/release-gate-batch-runner.js +19 -6
- package/dist/core/release/release-gate-dag.js +33 -6
- package/dist/core/release/release-gate-node.js +1 -3
- package/dist/core/release/release-gate-output-contract.js +55 -0
- package/dist/core/release/release-pack-contract.js +1 -1
- package/dist/core/release/release-pack-receipt.js +209 -8
- package/dist/core/release/release-real-contract.js +1 -1
- package/dist/core/release/stage-publish.js +106 -28
- package/dist/core/retention/temp-lease.js +204 -0
- package/dist/core/retention.js +18 -195
- package/dist/core/routes.js +1 -2
- package/dist/core/safety/intent-contract/intent-contract.js +3 -3
- package/dist/core/subagents/naruto-host-credentials.js +6 -1
- package/dist/core/subagents/official-subagent-config.js +27 -9
- package/dist/core/subagents/official-subagent-runner.js +14 -31
- package/dist/core/subagents/wave-lifecycle.js +36 -8
- package/dist/core/subagents/wave-parent-guidance.js +2 -1
- package/dist/core/telegram/doctor.js +2 -2
- package/dist/core/telegram/liveness.js +10 -4
- package/dist/core/triwiki/context-graph/compiler/index.js +18 -18
- package/dist/core/update/update-migration-state/fast-mode-config.js +172 -0
- package/dist/core/update/update-migration-state/session-state-split.js +247 -0
- package/dist/core/update/update-migration-state/simple-stages.js +46 -0
- package/dist/core/update/update-migration-state.js +4 -481
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +28 -4
- package/dist/native/sks-menubar/Sources/OperationModels.swift +112 -2
- package/dist/native/sks-menubar/Sources/ProviderRouteExplanation.swift +21 -0
- package/dist/native/sks-menubar/Sources/ProvidersBridgeCatalog.swift +140 -0
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +107 -470
- package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +27 -197
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +335 -269
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +233 -437
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +39 -9
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
- package/dist/scripts/canonical-test-runner.js +7 -1
- package/dist/scripts/check-runtime-schemas.js +7 -2
- package/dist/scripts/codex-sdk-real-smoke-check.js +3 -1
- package/dist/scripts/desktop-bridge-capabilities-check.js +123 -0
- package/dist/scripts/{codex-lb-desktop-bridge-latency-check.js → desktop-bridge-latency-check.js} +61 -16
- package/dist/scripts/desktop-bridge-real-evidence-check.js +64 -0
- package/dist/scripts/{codex-lb-desktop-bridge-check.js → desktop-bridge-transport-check.js} +92 -155
- package/dist/scripts/desktop-bridge-transport-client.js +116 -0
- package/dist/scripts/desktop-bridge-unification-check.js +276 -0
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -8
- package/dist/scripts/gate-policy-audit-check.js +52 -2
- package/dist/scripts/provider-badge-context-check.js +55 -12
- package/dist/scripts/publish-preflight.js +1 -2
- package/dist/scripts/release-affected-selector-check.js +1 -2
- package/dist/scripts/release-aggressive-resource-governor-check.js +1 -1
- package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
- package/dist/scripts/release-cache-version-neutral-fixture-check.js +1 -0
- package/dist/scripts/release-full-parallelism-blackbox.js +1 -1
- package/dist/scripts/release-gate-batch-runner-check.js +9 -0
- package/dist/scripts/release-gate-dag-runner-check.js +6 -0
- package/dist/scripts/release-gate-dag-runner.js +4 -2
- package/dist/scripts/release-metadata-check.js +16 -3
- package/dist/scripts/release-pack-receipt.js +29 -6
- package/dist/scripts/release-physical-gates-check.js +39 -0
- package/dist/scripts/release-real-check.js +7 -5
- package/package.json +6 -8
- package/release-gates.v2.json +156 -157
- package/runtime-required-scripts.json +11 -11
- package/schemas/codex/desktop-bridge-status-v3.schema.json +286 -0
- package/schemas/codex/desktop-capabilities-v3.schema.json +198 -0
- package/schemas/release/release-gate-node.schema.json +2 -3
- package/dist/cli/codex-lb-setup-warning-output.js +0 -17
- package/dist/cli/install-helpers-codex-lb-chain.js +0 -529
- package/dist/commands/codex-lb.js +0 -1232
- package/dist/core/architecture-hardening/migration/migration.js +0 -111
- package/dist/core/codex-app/auxiliary-oauth-policy.js +0 -36
- package/dist/core/codex-app/catalog-compat/catalog-service.js +0 -104
- package/dist/core/codex-app/child-policy/child-policy.js +0 -35
- package/dist/core/codex-app/desktop-routing-snapshot.js +0 -233
- package/dist/core/codex-app/glm-model-profile.js +0 -1
- package/dist/core/codex-app/glm-profile-installer.js +0 -191
- package/dist/core/codex-app/glm-profile-schema.js +0 -42
- package/dist/core/codex-app/multi-provider-router.js +0 -391
- package/dist/core/codex-app/openrouter-activate.js +0 -405
- package/dist/core/codex-app/openrouter-model-catalog.js +0 -217
- package/dist/core/codex-app/provider-child-policy.js +0 -64
- package/dist/core/codex-app/provider-mode.js +0 -91
- package/dist/core/codex-app/provider-session-policy.js +0 -85
- package/dist/core/codex-app/session-policy/session-pinning.js +0 -68
- package/dist/core/codex-control/codex-lb-launch-recovery.js +0 -319
- package/dist/core/codex-lb/codex-lb-setup.js +0 -293
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +0 -456
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +0 -290
- package/dist/core/codex-lb/desktop-center-credentials.js +0 -1083
- package/dist/core/codex-lb/desktop-mode.js +0 -32
- package/dist/core/codex-lb/desktop-real-evidence.js +0 -168
- package/dist/core/codex-lb/legacy-migration.js +0 -576
- package/dist/core/codex-lb/native-openai-transport/transport.js +0 -68
- package/dist/core/codex-lb/provider-routing/provider-router.js +0 -58
- package/dist/core/codex-lb/routing-truth.js +0 -327
- package/dist/core/imagegen/codex-lb-imagegen-target.js +0 -92
- package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +0 -118
- package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +0 -17
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +0 -331
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +0 -260
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +0 -120
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +0 -169
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +0 -287
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +0 -123
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +0 -114
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +0 -152
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +0 -231
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import { RELEASE_ORIGIN_IDENTITY, releaseOriginIdentity } from './release-origin.js';
|
|
4
5
|
import { exactNpmStageCliInvocation, localNpmStageReviewEnvironmentBlocker, REQUIRED_NPM_STAGE_CLI_VERSION } from './npm-stage-contract.js';
|
|
6
|
+
import { PHYSICAL_RELEASE_EVIDENCE_REPOSITORY } from './physical-release-gates.js';
|
|
7
|
+
import { NPM_STAGE_PUBLISH_RECEIPT_SCHEMA, STAGE_DISPATCH_NONCE_PATTERN } from './npm-stage-tarball-verifier-support.js';
|
|
5
8
|
export const STAGE_PUBLISH_SCHEMA = 'sks.release-stage-publish.v1';
|
|
6
9
|
export const STAGE_WORKFLOW_FILE = 'publish-npm.yml';
|
|
7
10
|
export const RELEASE_BRANCH = 'main';
|
|
11
|
+
export const STAGE_RUN_NAME_PREFIX = 'npm-stage';
|
|
8
12
|
const NPM_REGISTRY = 'https://registry.npmjs.org/';
|
|
9
13
|
const STAGE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
14
|
export function stagePublish(opts) {
|
|
@@ -12,20 +16,27 @@ export function stagePublish(opts) {
|
|
|
12
16
|
const steps = [];
|
|
13
17
|
const packageIdentity = readPackageIdentity(opts.root, readJsonFile);
|
|
14
18
|
const version = String(opts.version || packageIdentity.version || '').trim();
|
|
19
|
+
const physicalEvidenceRunId = String(opts.physicalEvidenceRunId || '').trim();
|
|
15
20
|
const confirmed = opts.confirm === true;
|
|
16
21
|
let commit = null;
|
|
17
22
|
let runId = null;
|
|
18
23
|
let stageId = null;
|
|
24
|
+
let dispatchNonce = null;
|
|
19
25
|
const preflight = runPreflight(opts, packageIdentity.name, version);
|
|
20
26
|
steps.push(...preflight.steps);
|
|
21
27
|
commit = preflight.commit;
|
|
22
28
|
if (!preflight.ok)
|
|
23
29
|
return finish();
|
|
24
30
|
if (!confirmed) {
|
|
25
|
-
steps.push(skipped('push', 'confirm_required'), skipped('run_snapshot', 'confirm_required'), skipped('dispatch', 'confirm_required'));
|
|
31
|
+
steps.push(skipped('dispatch_nonce', 'confirm_required'), skipped('push', 'confirm_required'), skipped('run_snapshot', 'confirm_required'), skipped('dispatch', 'confirm_required'));
|
|
26
32
|
steps.push(skipped('watch', 'confirm_required'), skipped('download', 'confirm_required'), skipped('verify', 'confirm_required'));
|
|
27
33
|
return finish();
|
|
28
34
|
}
|
|
35
|
+
dispatchNonce = String((opts.generateDispatchNonce || defaultDispatchNonce)()).trim();
|
|
36
|
+
const dispatchNonceOk = STAGE_DISPATCH_NONCE_PATTERN.test(dispatchNonce);
|
|
37
|
+
steps.push(step('dispatch_nonce', dispatchNonceOk, dispatchNonceOk ? dispatchNonce : null, dispatchNonceOk ? null : 'stage_dispatch_nonce_invalid'));
|
|
38
|
+
if (!dispatchNonceOk)
|
|
39
|
+
return finish();
|
|
29
40
|
const push = opts.run('git', ['push', 'origin', RELEASE_BRANCH]);
|
|
30
41
|
steps.push(step('push', push.status === 0, `git push origin ${RELEASE_BRANCH}`, push.status === 0 ? null : 'stage_push_failed'));
|
|
31
42
|
if (push.status !== 0)
|
|
@@ -38,13 +49,16 @@ export function stagePublish(opts) {
|
|
|
38
49
|
'workflow', 'run', STAGE_WORKFLOW_FILE,
|
|
39
50
|
'--ref', RELEASE_BRANCH,
|
|
40
51
|
'-f', `version=${version}`,
|
|
52
|
+
'-f', `dispatch_nonce=${dispatchNonce}`,
|
|
53
|
+
'-f', `physical_evidence_run_id=${physicalEvidenceRunId}`,
|
|
41
54
|
'-f', 'confirm_stage=true'
|
|
42
55
|
]);
|
|
43
|
-
steps.push(step('dispatch', dispatch.status === 0, `gh workflow run ${STAGE_WORKFLOW_FILE} version=${version} confirm_stage=true`, dispatch.status === 0 ? null : 'stage_dispatch_failed'));
|
|
56
|
+
steps.push(step('dispatch', dispatch.status === 0, `gh workflow run ${STAGE_WORKFLOW_FILE} version=${version} dispatch_nonce=${dispatchNonce} physical_evidence_run_id=${physicalEvidenceRunId} confirm_stage=true`, dispatch.status === 0 ? null : 'stage_dispatch_failed'));
|
|
44
57
|
if (dispatch.status !== 0)
|
|
45
58
|
return finish();
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
const resolvedRun = resolveRunId(opts, commit, priorRuns.ids, stageRunDisplayTitle(version, dispatchNonce, physicalEvidenceRunId));
|
|
60
|
+
runId = resolvedRun.runId;
|
|
61
|
+
steps.push(step('resolve_run', Boolean(runId), runId ? `run ${runId}` : null, runId ? null : resolvedRun.blocker));
|
|
48
62
|
if (!runId)
|
|
49
63
|
return finish();
|
|
50
64
|
const watch = opts.run('gh', ['run', 'watch', runId, '--exit-status'], { timeoutMs: opts.watchTimeoutMs ?? 3 * 60 * 60 * 1000 });
|
|
@@ -52,11 +66,11 @@ export function stagePublish(opts) {
|
|
|
52
66
|
if (watch.status !== 0)
|
|
53
67
|
return finish();
|
|
54
68
|
const artifactDir = opts.artifactDir || path.join(opts.root, '.sneakoscope', 'reports', 'release', version, 'stage');
|
|
55
|
-
const download = downloadArtifacts(opts, runId, commit, artifactDir);
|
|
69
|
+
const download = downloadArtifacts(opts, runId, commit, dispatchNonce, artifactDir);
|
|
56
70
|
steps.push(download.step);
|
|
57
71
|
if (!download.step.ok)
|
|
58
72
|
return finish();
|
|
59
|
-
const receipt = readStageReceipt(artifactDir, commit, readJsonFile);
|
|
73
|
+
const receipt = readStageReceipt(artifactDir, commit, dispatchNonce, physicalEvidenceRunId, runId, readJsonFile);
|
|
60
74
|
stageId = receipt.stageId;
|
|
61
75
|
steps.push(step('stage_receipt', Boolean(stageId), receipt.path, stageId ? null : receipt.blocker));
|
|
62
76
|
if (!stageId || !receipt.path)
|
|
@@ -65,7 +79,10 @@ export function stagePublish(opts) {
|
|
|
65
79
|
handoffDir: receipt.handoffDir,
|
|
66
80
|
stageReceiptPath: receipt.path,
|
|
67
81
|
version,
|
|
68
|
-
stageId
|
|
82
|
+
stageId,
|
|
83
|
+
dispatchNonce,
|
|
84
|
+
physicalEvidenceRunId,
|
|
85
|
+
workflowRunId: runId
|
|
69
86
|
});
|
|
70
87
|
steps.push(verify);
|
|
71
88
|
return finish();
|
|
@@ -77,6 +94,9 @@ export function stagePublish(opts) {
|
|
|
77
94
|
ok: blockers.length === 0 && (confirmed ? reachedApproval : true),
|
|
78
95
|
confirmed,
|
|
79
96
|
version,
|
|
97
|
+
release_tag: `v${version}`,
|
|
98
|
+
physical_evidence_run_id: /^\d+$/.test(physicalEvidenceRunId) ? physicalEvidenceRunId : null,
|
|
99
|
+
dispatch_nonce: dispatchNonce,
|
|
80
100
|
commit,
|
|
81
101
|
run_id: runId,
|
|
82
102
|
stage_id: stageId,
|
|
@@ -104,11 +124,24 @@ function runPreflight(opts, packageName, version) {
|
|
|
104
124
|
const steps = [];
|
|
105
125
|
const branch = text(opts.run('git', ['rev-parse', '--abbrev-ref', 'HEAD']));
|
|
106
126
|
steps.push(step('branch', branch === RELEASE_BRANCH, branch, branch === RELEASE_BRANCH ? null : 'stage_requires_main_branch'));
|
|
127
|
+
const commit = text(opts.run('git', ['rev-parse', 'HEAD'])) || null;
|
|
107
128
|
const status = opts.run('git', ['status', '--porcelain']);
|
|
108
129
|
const clean = status.status === 0 && text(status) === '';
|
|
109
130
|
steps.push(step('clean_tree', clean, clean ? 'clean' : 'dirty', clean ? null : 'stage_requires_clean_tree'));
|
|
110
131
|
const versionOk = /^\d+\.\d+\.\d+$/.test(version);
|
|
111
132
|
steps.push(step('version', versionOk, version || null, versionOk ? null : 'stage_version_invalid'));
|
|
133
|
+
const releaseTag = versionOk ? `v${version}` : '';
|
|
134
|
+
const localTag = releaseTag
|
|
135
|
+
? text(opts.run('git', ['rev-parse', `refs/tags/${releaseTag}^{commit}`]))
|
|
136
|
+
: '';
|
|
137
|
+
const localTagOk = Boolean(commit) && localTag === commit;
|
|
138
|
+
steps.push(step('local_release_tag', localTagOk, localTag || releaseTag || null, localTagOk ? null : 'stage_local_release_tag_mismatch'));
|
|
139
|
+
const remoteTagResult = releaseTag
|
|
140
|
+
? opts.run('git', ['ls-remote', '--exit-code', 'origin', `refs/tags/${releaseTag}`, `refs/tags/${releaseTag}^{}`])
|
|
141
|
+
: { status: 1, stdout: '', stderr: '' };
|
|
142
|
+
const remoteTag = remoteTagResult.status === 0 ? remoteReleaseTagCommit(remoteTagResult.stdout, releaseTag) : '';
|
|
143
|
+
const remoteTagOk = Boolean(commit) && remoteTag === commit;
|
|
144
|
+
steps.push(step('remote_release_tag', remoteTagOk, remoteTag || releaseTag || null, remoteTagOk ? null : 'stage_remote_release_tag_mismatch'));
|
|
112
145
|
const packageNameOk = packageName.length > 0;
|
|
113
146
|
steps.push(step('package_name', packageNameOk, packageName || null, packageNameOk ? null : 'stage_package_name_invalid'));
|
|
114
147
|
const origin = releaseOriginIdentity(opts.root);
|
|
@@ -122,11 +155,27 @@ function runPreflight(opts, packageName, version) {
|
|
|
122
155
|
const verifierReady = fs.existsSync(verifier);
|
|
123
156
|
steps.push(step('local_review_verifier', verifierReady, verifierReady ? verifier : null, verifierReady ? null : 'stage_verifier_unavailable_outside_checkout'));
|
|
124
157
|
const physicalVerifier = path.join(opts.root, 'dist', 'scripts', 'release-physical-gates-check.js');
|
|
125
|
-
const
|
|
158
|
+
const physicalEvidenceRunId = String(opts.physicalEvidenceRunId || '').trim();
|
|
159
|
+
const physicalEvidenceRunIdReady = /^\d+$/.test(physicalEvidenceRunId);
|
|
160
|
+
steps.push(step('physical_evidence_run_id', physicalEvidenceRunIdReady, physicalEvidenceRunId || null, physicalEvidenceRunIdReady ? null : 'stage_physical_evidence_run_id_invalid'));
|
|
161
|
+
const physicalArgs = [
|
|
162
|
+
physicalVerifier,
|
|
163
|
+
...(opts.physicalEvidenceArchive ? ['--archive', path.resolve(opts.root, opts.physicalEvidenceArchive)] : []),
|
|
164
|
+
'--evidence-run-id', physicalEvidenceRunId,
|
|
165
|
+
'--repository', PHYSICAL_RELEASE_EVIDENCE_REPOSITORY
|
|
166
|
+
];
|
|
167
|
+
const physical = physicalEvidenceRunIdReady
|
|
168
|
+
? opts.run(process.execPath, physicalArgs)
|
|
169
|
+
: { status: 1, stdout: '', stderr: 'physical evidence run id missing or invalid' };
|
|
126
170
|
const physicalReport = safeJsonObject(physical.stdout);
|
|
127
|
-
const physicalReady = physical.status === 0
|
|
171
|
+
const physicalReady = physical.status === 0
|
|
172
|
+
&& physicalReport?.schema === 'sks.release-physical-gates-inspection.v2'
|
|
173
|
+
&& physicalReport?.ok === true
|
|
174
|
+
&& physicalReport?.status === 'passed'
|
|
175
|
+
&& physicalReport?.release_authorizing === true
|
|
176
|
+
&& physicalReport?.inspector_platform === 'darwin';
|
|
128
177
|
steps.push(step('physical_release_gates', physicalReady, physicalReady
|
|
129
|
-
? 'all
|
|
178
|
+
? 'all five source-bound physical release receipts and live Desktop Bridge artifacts verified on macOS'
|
|
130
179
|
: compactPhysicalGateFailure(physical, physicalReport), physicalReady ? null : 'stage_physical_release_gates_invalid'));
|
|
131
180
|
const reviewEnvironmentBlocker = localNpmStageReviewEnvironmentBlocker(opts.env || process.env);
|
|
132
181
|
steps.push(step('local_review_environment', reviewEnvironmentBlocker === null, reviewEnvironmentBlocker === null ? 'maintainer-local' : reviewEnvironmentBlocker, reviewEnvironmentBlocker === null ? null : `stage_${reviewEnvironmentBlocker}`));
|
|
@@ -184,30 +233,44 @@ function runPreflight(opts, packageName, version) {
|
|
|
184
233
|
}
|
|
185
234
|
const gh = opts.run('gh', ['auth', 'status']);
|
|
186
235
|
steps.push(step('gh_auth', gh.status === 0, gh.status === 0 ? 'authenticated' : null, gh.status === 0 ? null : 'stage_gh_not_authenticated'));
|
|
187
|
-
const commit = text(opts.run('git', ['rev-parse', 'HEAD'])) || null;
|
|
188
236
|
return { ok: steps.every((entry) => entry.ok), commit, steps };
|
|
189
237
|
}
|
|
190
|
-
function
|
|
238
|
+
function remoteReleaseTagCommit(output, tag) {
|
|
239
|
+
const rows = String(output || '').trim().split(/\r?\n/).filter(Boolean);
|
|
240
|
+
const peeled = rows.find((row) => row.endsWith(`refs/tags/${tag}^{}`));
|
|
241
|
+
return text({ status: 0, stdout: peeled || rows[0] || '', stderr: '' }).split(/\s+/, 1)[0] || '';
|
|
242
|
+
}
|
|
243
|
+
export function stageRunDisplayTitle(version, dispatchNonce, physicalEvidenceRunId) {
|
|
244
|
+
return `${STAGE_RUN_NAME_PREFIX}-${version}-${dispatchNonce}-physical-${physicalEvidenceRunId}`;
|
|
245
|
+
}
|
|
246
|
+
function defaultDispatchNonce() {
|
|
247
|
+
return crypto.randomBytes(16).toString('hex');
|
|
248
|
+
}
|
|
249
|
+
function resolveRunId(opts, commit, priorRunIds, expectedDisplayTitle) {
|
|
191
250
|
if (!commit)
|
|
192
|
-
return null;
|
|
251
|
+
return { runId: null, blocker: 'stage_run_not_found' };
|
|
193
252
|
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
194
253
|
const listed = listWorkflowRuns(opts);
|
|
195
254
|
if (listed.status === 0) {
|
|
196
255
|
const rows = safeJsonArray(listed.stdout);
|
|
197
|
-
const
|
|
256
|
+
const matches = rows.filter((row) => {
|
|
198
257
|
const id = String(row?.databaseId ?? '');
|
|
199
258
|
const event = String(row?.event || '');
|
|
200
259
|
return String(row?.headSha || '') === commit
|
|
260
|
+
&& String(row?.displayTitle || '') === expectedDisplayTitle
|
|
201
261
|
&& id.length > 0
|
|
202
262
|
&& !priorRunIds.has(id)
|
|
203
|
-
&&
|
|
263
|
+
&& event === 'workflow_dispatch';
|
|
204
264
|
});
|
|
205
|
-
if (
|
|
206
|
-
return
|
|
265
|
+
if (matches.length > 1)
|
|
266
|
+
return { runId: null, blocker: 'stage_run_ambiguous' };
|
|
267
|
+
if (matches[0]?.databaseId != null) {
|
|
268
|
+
return { runId: String(matches[0].databaseId), blocker: 'stage_run_not_found' };
|
|
269
|
+
}
|
|
207
270
|
}
|
|
208
271
|
opts.run('sleep', ['2']);
|
|
209
272
|
}
|
|
210
|
-
return null;
|
|
273
|
+
return { runId: null, blocker: 'stage_run_not_found' };
|
|
211
274
|
}
|
|
212
275
|
function snapshotWorkflowRunIds(opts, commit) {
|
|
213
276
|
if (!commit)
|
|
@@ -226,12 +289,12 @@ function listWorkflowRuns(opts) {
|
|
|
226
289
|
'run', 'list',
|
|
227
290
|
'--workflow', STAGE_WORKFLOW_FILE,
|
|
228
291
|
'--branch', RELEASE_BRANCH,
|
|
229
|
-
'--limit', '
|
|
230
|
-
'--json', 'databaseId,headSha,status,event'
|
|
292
|
+
'--limit', '100',
|
|
293
|
+
'--json', 'databaseId,headSha,status,event,displayTitle'
|
|
231
294
|
]);
|
|
232
295
|
}
|
|
233
|
-
function downloadArtifacts(opts, runId, commit, dir) {
|
|
234
|
-
const names = stageArtifactNames(commit);
|
|
296
|
+
function downloadArtifacts(opts, runId, commit, dispatchNonce, dir) {
|
|
297
|
+
const names = stageArtifactNames(commit, dispatchNonce);
|
|
235
298
|
if (!names)
|
|
236
299
|
return { step: step('download', false, null, 'stage_commit_unknown') };
|
|
237
300
|
const args = ['run', 'download', runId, '--dir', dir];
|
|
@@ -240,16 +303,16 @@ function downloadArtifacts(opts, runId, commit, dir) {
|
|
|
240
303
|
const result = opts.run('gh', args);
|
|
241
304
|
return { step: step('download', result.status === 0, dir, result.status === 0 ? null : 'stage_artifact_download_failed') };
|
|
242
305
|
}
|
|
243
|
-
function stageArtifactNames(commit) {
|
|
244
|
-
if (!commit)
|
|
306
|
+
function stageArtifactNames(commit, dispatchNonce) {
|
|
307
|
+
if (!commit || !STAGE_DISPATCH_NONCE_PATTERN.test(dispatchNonce))
|
|
245
308
|
return null;
|
|
246
309
|
return {
|
|
247
|
-
handoff: `stage-input-${commit}`,
|
|
248
|
-
receipt: `npm-stage-receipt-${commit}`
|
|
310
|
+
handoff: `stage-input-${commit}-${dispatchNonce}`,
|
|
311
|
+
receipt: `npm-stage-receipt-${commit}-${dispatchNonce}`
|
|
249
312
|
};
|
|
250
313
|
}
|
|
251
|
-
function readStageReceipt(dir, commit, readJsonFile) {
|
|
252
|
-
const names = stageArtifactNames(commit);
|
|
314
|
+
function readStageReceipt(dir, commit, dispatchNonce, physicalEvidenceRunId, workflowRunId, readJsonFile) {
|
|
315
|
+
const names = stageArtifactNames(commit, dispatchNonce);
|
|
253
316
|
const layouts = names
|
|
254
317
|
? [
|
|
255
318
|
{
|
|
@@ -266,9 +329,21 @@ function readStageReceipt(dir, commit, readJsonFile) {
|
|
|
266
329
|
const stageId = String(payload?.stage_id || '').trim();
|
|
267
330
|
if (!stageId)
|
|
268
331
|
continue;
|
|
332
|
+
if (payload?.schema !== NPM_STAGE_PUBLISH_RECEIPT_SCHEMA) {
|
|
333
|
+
return { stageId: null, path: file, handoffDir: layout.handoffDir, blocker: 'stage_receipt_schema_invalid' };
|
|
334
|
+
}
|
|
269
335
|
if (!STAGE_ID_RE.test(stageId)) {
|
|
270
336
|
return { stageId: null, path: file, handoffDir: layout.handoffDir, blocker: 'stage_id_uuid_invalid' };
|
|
271
337
|
}
|
|
338
|
+
if (String(payload?.dispatch_nonce || '') !== dispatchNonce) {
|
|
339
|
+
return { stageId: null, path: file, handoffDir: layout.handoffDir, blocker: 'stage_receipt_dispatch_nonce_mismatch' };
|
|
340
|
+
}
|
|
341
|
+
if (String(payload?.physical_evidence_run_id || '') !== physicalEvidenceRunId) {
|
|
342
|
+
return { stageId: null, path: file, handoffDir: layout.handoffDir, blocker: 'stage_receipt_physical_evidence_run_id_mismatch' };
|
|
343
|
+
}
|
|
344
|
+
if (String(payload?.workflow_run_id || '') !== workflowRunId) {
|
|
345
|
+
return { stageId: null, path: file, handoffDir: layout.handoffDir, blocker: 'stage_receipt_workflow_run_id_mismatch' };
|
|
346
|
+
}
|
|
272
347
|
return { stageId, path: file, handoffDir: layout.handoffDir, blocker: null };
|
|
273
348
|
}
|
|
274
349
|
}
|
|
@@ -281,6 +356,9 @@ function runLocalVerify(opts, input) {
|
|
|
281
356
|
const result = opts.run(process.execPath, [
|
|
282
357
|
verifier,
|
|
283
358
|
'--stage-id', input.stageId,
|
|
359
|
+
'--dispatch-nonce', input.dispatchNonce,
|
|
360
|
+
'--physical-evidence-run-id', input.physicalEvidenceRunId,
|
|
361
|
+
'--workflow-run-id', input.workflowRunId,
|
|
284
362
|
'--local-receipt', path.join(input.handoffDir, 'pack-receipt.json'),
|
|
285
363
|
'--local-tarball', path.join(input.handoffDir, `sneakoscope-${input.version}.tgz`),
|
|
286
364
|
'--stage-receipt', input.stageReceiptPath
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { exists, readJson, rmrf, SKS_TEMP_LEASE_FILE } from '../fsx.js';
|
|
5
|
+
const TEMP_RETENTION_SCAN_MAX_ENTRIES = 100_000;
|
|
6
|
+
const TEMP_RETENTION_SCAN_MAX_DEPTH = 20;
|
|
7
|
+
export async function inspectTempPath(target) {
|
|
8
|
+
const rootStat = await fs.lstat(target).catch(() => null);
|
|
9
|
+
if (!rootStat || rootStat.isSymbolicLink()) {
|
|
10
|
+
return {
|
|
11
|
+
complete: false,
|
|
12
|
+
latestMtimeMs: 0,
|
|
13
|
+
bytes: 0,
|
|
14
|
+
blockers: [rootStat ? 'temp_path_is_symlink' : 'temp_path_missing_or_unreadable']
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
let latestMtimeMs = rootStat.mtimeMs;
|
|
18
|
+
let bytes = rootStat.isFile() ? rootStat.size : 0;
|
|
19
|
+
let entryCount = 0;
|
|
20
|
+
const blockers = [];
|
|
21
|
+
const stack = rootStat.isDirectory()
|
|
22
|
+
? [{ dir: target, depth: 0 }]
|
|
23
|
+
: [];
|
|
24
|
+
while (stack.length) {
|
|
25
|
+
const current = stack.pop();
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = await fs.readdir(current.dir, { withFileTypes: true });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
blockers.push('temp_path_readdir_failed');
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
entryCount += 1;
|
|
36
|
+
if (entryCount > TEMP_RETENTION_SCAN_MAX_ENTRIES) {
|
|
37
|
+
blockers.push(`temp_path_max_entries_exceeded:${TEMP_RETENTION_SCAN_MAX_ENTRIES}`);
|
|
38
|
+
return { complete: false, latestMtimeMs, bytes, blockers: [...new Set(blockers)] };
|
|
39
|
+
}
|
|
40
|
+
const child = path.join(current.dir, entry.name);
|
|
41
|
+
const stat = await fs.lstat(child).catch(() => null);
|
|
42
|
+
if (!stat) {
|
|
43
|
+
blockers.push('temp_path_stat_failed');
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
latestMtimeMs = Math.max(latestMtimeMs, stat.mtimeMs);
|
|
47
|
+
if (stat.isSymbolicLink())
|
|
48
|
+
continue;
|
|
49
|
+
if (stat.isDirectory()) {
|
|
50
|
+
if (current.depth >= TEMP_RETENTION_SCAN_MAX_DEPTH) {
|
|
51
|
+
blockers.push(`temp_path_max_depth_exceeded:${TEMP_RETENTION_SCAN_MAX_DEPTH}`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
stack.push({ dir: child, depth: current.depth + 1 });
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (stat.isFile())
|
|
59
|
+
bytes += stat.size;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
complete: blockers.length === 0,
|
|
64
|
+
latestMtimeMs,
|
|
65
|
+
bytes,
|
|
66
|
+
blockers: [...new Set(blockers)]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function currentProcessOwns(stat) {
|
|
70
|
+
if (typeof process.getuid !== 'function')
|
|
71
|
+
return true;
|
|
72
|
+
return stat.uid === process.getuid();
|
|
73
|
+
}
|
|
74
|
+
export function sharedTempEntryMatchesProject(entryName, projectHash) {
|
|
75
|
+
return entryName === `sks-${projectHash}` || entryName.startsWith(`sks-${projectHash}-`);
|
|
76
|
+
}
|
|
77
|
+
export function activeTempEnvironmentKey(target) {
|
|
78
|
+
const resolvedTarget = path.resolve(target);
|
|
79
|
+
for (const key of ['SKS_TMP_DIR', 'TMPDIR', 'TMP', 'TEMP']) {
|
|
80
|
+
const raw = process.env[key];
|
|
81
|
+
if (!raw)
|
|
82
|
+
continue;
|
|
83
|
+
const activePath = path.resolve(raw);
|
|
84
|
+
if (isWithin(resolvedTarget, activePath))
|
|
85
|
+
return key;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
export async function liveTempLease(target) {
|
|
90
|
+
const leasePath = path.join(target, SKS_TEMP_LEASE_FILE);
|
|
91
|
+
const stat = await fs.lstat(leasePath).catch(() => null);
|
|
92
|
+
if (!stat?.isFile() || stat.isSymbolicLink() || !currentProcessOwns(stat))
|
|
93
|
+
return null;
|
|
94
|
+
const lease = await readJson(leasePath, null).catch(() => null);
|
|
95
|
+
const pid = Number(lease?.pid);
|
|
96
|
+
if (lease?.schema !== 'sks.temp-lease.v1' || !processIdAlive(pid))
|
|
97
|
+
return null;
|
|
98
|
+
return {
|
|
99
|
+
path: leasePath,
|
|
100
|
+
pid,
|
|
101
|
+
kind: lease?.kind ? String(lease.kind) : null
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export async function removeDeadCanonicalTestLease(base, target, targetStat, dryRun, actions) {
|
|
105
|
+
if (!targetStat.isDirectory() || targetStat.isSymbolicLink() || !currentProcessOwns(targetStat))
|
|
106
|
+
return false;
|
|
107
|
+
const [realBase, realTarget] = await Promise.all([
|
|
108
|
+
fs.realpath(base).catch(() => null),
|
|
109
|
+
fs.realpath(target).catch(() => null)
|
|
110
|
+
]);
|
|
111
|
+
if (!realBase || !realTarget || path.dirname(realTarget) !== realBase || !isWithin(realBase, realTarget))
|
|
112
|
+
return false;
|
|
113
|
+
const leasePath = path.join(target, SKS_TEMP_LEASE_FILE);
|
|
114
|
+
const leaseLstat = await fs.lstat(leasePath).catch(() => null);
|
|
115
|
+
if (!leaseLstat?.isFile()
|
|
116
|
+
|| leaseLstat.isSymbolicLink()
|
|
117
|
+
|| !currentProcessOwns(leaseLstat)
|
|
118
|
+
|| leaseLstat.size > 4096)
|
|
119
|
+
return false;
|
|
120
|
+
const leaseHandle = await fs.open(leasePath, 'r').catch(() => null);
|
|
121
|
+
if (!leaseHandle)
|
|
122
|
+
return false;
|
|
123
|
+
let lease = null;
|
|
124
|
+
try {
|
|
125
|
+
const leaseStat = await leaseHandle.stat();
|
|
126
|
+
if (!leaseStat.isFile()
|
|
127
|
+
|| !currentProcessOwns(leaseStat)
|
|
128
|
+
|| leaseStat.dev !== leaseLstat.dev
|
|
129
|
+
|| leaseStat.ino !== leaseLstat.ino)
|
|
130
|
+
return false;
|
|
131
|
+
lease = JSON.parse(await leaseHandle.readFile({ encoding: 'utf8' }));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await leaseHandle.close().catch(() => undefined);
|
|
138
|
+
}
|
|
139
|
+
const leaseKeys = lease && typeof lease === 'object' && !Array.isArray(lease)
|
|
140
|
+
? Object.keys(lease).sort()
|
|
141
|
+
: [];
|
|
142
|
+
const pid = Number(lease?.pid);
|
|
143
|
+
if (lease?.schema !== 'sks.temp-lease.v1'
|
|
144
|
+
|| lease?.kind !== 'canonical-test-runner'
|
|
145
|
+
|| !Number.isSafeInteger(pid)
|
|
146
|
+
|| pid <= 0
|
|
147
|
+
|| typeof lease?.created_at !== 'string'
|
|
148
|
+
|| !Number.isFinite(Date.parse(lease.created_at))
|
|
149
|
+
|| leaseKeys.join(',') !== 'created_at,kind,pid,schema'
|
|
150
|
+
|| processIdAlive(pid))
|
|
151
|
+
return false;
|
|
152
|
+
const currentTargetStat = await fs.lstat(target).catch(() => null);
|
|
153
|
+
if (!currentTargetStat?.isDirectory()
|
|
154
|
+
|| currentTargetStat.isSymbolicLink()
|
|
155
|
+
|| !currentProcessOwns(currentTargetStat)
|
|
156
|
+
|| currentTargetStat.dev !== targetStat.dev
|
|
157
|
+
|| currentTargetStat.ino !== targetStat.ino)
|
|
158
|
+
return false;
|
|
159
|
+
const action = {
|
|
160
|
+
action: 'remove_sks_temp',
|
|
161
|
+
path: target,
|
|
162
|
+
reason: 'dead_canonical_test_lease',
|
|
163
|
+
lease_path: leasePath,
|
|
164
|
+
owner_pid: pid,
|
|
165
|
+
lease_kind: lease.kind
|
|
166
|
+
};
|
|
167
|
+
if (dryRun) {
|
|
168
|
+
actions.push(action);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
const quarantine = path.join(realBase, `.sks-retention-quarantine-${process.pid}-${randomUUID()}`);
|
|
172
|
+
try {
|
|
173
|
+
await fs.rename(target, quarantine);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const quarantinedStat = await fs.lstat(quarantine).catch(() => null);
|
|
179
|
+
if (!quarantinedStat
|
|
180
|
+
|| quarantinedStat.dev !== targetStat.dev
|
|
181
|
+
|| quarantinedStat.ino !== targetStat.ino) {
|
|
182
|
+
if (!(await exists(target)))
|
|
183
|
+
await fs.rename(quarantine, target).catch(() => undefined);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
await rmrf(quarantine);
|
|
187
|
+
actions.push(action);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
function processIdAlive(pid) {
|
|
191
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
192
|
+
return false;
|
|
193
|
+
try {
|
|
194
|
+
process.kill(pid, 0);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
return error?.code === 'EPERM';
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function isWithin(parent, candidate) {
|
|
202
|
+
const rel = path.relative(path.resolve(parent), path.resolve(candidate));
|
|
203
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
204
|
+
}
|