@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18
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 +134 -15
- package/assets/integrations/skills/pappra/SKILL.md +28 -7
- package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
- package/assets/integrations/skills/pappra/references/environment.md +360 -0
- package/assets/integrations/skills/pappra/references/hosts.md +20 -0
- package/assets/integrations/skills/pappra/references/operations.md +36 -5
- package/dist/adapters/pi/recovery-extension.d.ts +65 -0
- package/dist/adapters/pi/recovery-extension.js +238 -0
- package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
- package/dist/adapters/pi/session-transition-extension.js +265 -0
- package/dist/app/automatic-handoff-events.d.ts +26 -0
- package/dist/app/automatic-handoff-events.js +134 -0
- package/dist/app/bounded-file.d.ts +2 -0
- package/dist/app/bounded-file.js +2 -0
- package/dist/app/capability-evidence/claude.d.ts +37 -0
- package/dist/app/capability-evidence/claude.js +206 -0
- package/dist/app/capability-evidence/codex.d.ts +35 -0
- package/dist/app/capability-evidence/codex.js +193 -0
- package/dist/app/capability-evidence/index.d.ts +33 -0
- package/dist/app/capability-evidence/index.js +117 -0
- package/dist/app/capability-evidence/pi.d.ts +73 -0
- package/dist/app/capability-evidence/pi.js +669 -0
- package/dist/app/capability-evidence/shared.d.ts +69 -0
- package/dist/app/capability-evidence/shared.js +195 -0
- package/dist/app/collab-auto-handoff.d.ts +55 -0
- package/dist/app/collab-auto-handoff.js +384 -0
- package/dist/app/collab-bindings.d.ts +57 -0
- package/dist/app/collab-bindings.js +1006 -0
- package/dist/app/collab-bootstrap-transport.d.ts +163 -0
- package/dist/app/collab-bootstrap-transport.js +769 -0
- package/dist/app/collab-bootstrap.d.ts +137 -0
- package/dist/app/collab-bootstrap.js +3455 -0
- package/dist/app/collab-handoff.d.ts +12 -0
- package/dist/app/collab-handoff.js +511 -0
- package/dist/app/collab-messages.d.ts +2 -0
- package/dist/app/collab-messages.js +2 -0
- package/dist/app/collab-notify.d.ts +156 -0
- package/dist/app/collab-notify.js +1208 -0
- package/dist/app/collab-preflight.d.ts +34 -0
- package/dist/app/collab-preflight.js +96 -0
- package/dist/app/collab-recovery-executor.d.ts +161 -0
- package/dist/app/collab-recovery-executor.js +1153 -0
- package/dist/app/collab-recovery-projection.d.ts +103 -0
- package/dist/app/collab-recovery-projection.js +659 -0
- package/dist/app/collab-recovery.d.ts +25 -0
- package/dist/app/collab-recovery.js +429 -0
- package/dist/app/collab-resources.d.ts +16 -0
- package/dist/app/collab-resources.js +278 -0
- package/dist/app/collab-role-events.d.ts +22 -0
- package/dist/app/collab-role-events.js +329 -0
- package/dist/app/collab-role-orchestration.d.ts +53 -0
- package/dist/app/collab-role-orchestration.js +601 -0
- package/dist/app/collab-role-plan.d.ts +81 -0
- package/dist/app/collab-role-plan.js +504 -0
- package/dist/app/collab-role-repair.d.ts +109 -0
- package/dist/app/collab-role-repair.js +821 -0
- package/dist/app/collab-rollover.d.ts +263 -0
- package/dist/app/collab-rollover.js +1112 -0
- package/dist/app/collab-route.d.ts +64 -0
- package/dist/app/collab-route.js +240 -0
- package/dist/app/collab-session-transition.d.ts +221 -0
- package/dist/app/collab-session-transition.js +3334 -0
- package/dist/app/collab-state.d.ts +7 -0
- package/dist/app/collab-state.js +162 -0
- package/dist/app/collab-task-store.d.ts +22 -0
- package/dist/app/collab-task-store.js +204 -0
- package/dist/app/collab-work-execution.d.ts +127 -0
- package/dist/app/collab-work-execution.js +1027 -0
- package/dist/app/collab-work-plan.d.ts +87 -0
- package/dist/app/collab-work-plan.js +437 -0
- package/dist/app/collaboration.d.ts +8 -2
- package/dist/app/collaboration.js +101 -12
- package/dist/app/doctor.js +108 -5
- package/dist/app/host-mcp.js +34 -8
- package/dist/app/host-probe.js +4 -4
- package/dist/app/init.d.ts +58 -4
- package/dist/app/init.js +768 -155
- package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
- package/dist/app/managed-pi-bootstrap-admission.js +104 -0
- package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
- package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
- package/dist/app/managed-pi-delivery.d.ts +8 -0
- package/dist/app/managed-pi-delivery.js +110 -0
- package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
- package/dist/app/managed-pi-handoff-receiver.js +263 -0
- package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
- package/dist/app/managed-pi-handoff-reference.js +60 -0
- package/dist/app/managed-pi-herdr.d.ts +21 -0
- package/dist/app/managed-pi-herdr.js +185 -0
- package/dist/app/managed-pi-initial-admission.d.ts +28 -0
- package/dist/app/managed-pi-initial-admission.js +416 -0
- package/dist/app/managed-pi-initial-launch.d.ts +5 -0
- package/dist/app/managed-pi-initial-launch.js +61 -0
- package/dist/app/managed-pi-native-observation.d.ts +36 -0
- package/dist/app/managed-pi-native-observation.js +191 -0
- package/dist/app/managed-pi-readiness.d.ts +29 -0
- package/dist/app/managed-pi-readiness.js +288 -0
- package/dist/app/managed-pi-repair-stop.d.ts +21 -0
- package/dist/app/managed-pi-repair-stop.js +371 -0
- package/dist/app/managed-pi-resume-admission.d.ts +32 -0
- package/dist/app/managed-pi-resume-admission.js +618 -0
- package/dist/app/managed-pi-retirement.d.ts +28 -0
- package/dist/app/managed-pi-retirement.js +112 -0
- package/dist/app/managed-pi-sdk-control.d.ts +7 -0
- package/dist/app/managed-pi-sdk-control.js +169 -0
- package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
- package/dist/app/managed-pi-sdk-runtime.js +483 -0
- package/dist/app/managed-pi-source-admission.d.ts +14 -0
- package/dist/app/managed-pi-source-admission.js +323 -0
- package/dist/app/managed-pi-transition-host.d.ts +36 -0
- package/dist/app/managed-pi-transition-host.js +292 -0
- package/dist/app/managed-pi.d.ts +54 -0
- package/dist/app/managed-pi.js +248 -0
- package/dist/app/shell-readiness.d.ts +66 -0
- package/dist/app/shell-readiness.js +368 -0
- package/dist/app/task-pin.d.ts +2 -23
- package/dist/app/task-pin.js +2 -113
- package/dist/cli/commands/collaboration-binding.d.ts +13 -0
- package/dist/cli/commands/collaboration-binding.js +118 -0
- package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
- package/dist/cli/commands/collaboration-bootstrap.js +84 -0
- package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
- package/dist/cli/commands/collaboration-capabilities.js +127 -0
- package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
- package/dist/cli/commands/collaboration-handoff.js +102 -0
- package/dist/cli/commands/collaboration-message.d.ts +17 -0
- package/dist/cli/commands/collaboration-message.js +177 -0
- package/dist/cli/commands/collaboration-plan.d.ts +8 -0
- package/dist/cli/commands/collaboration-plan.js +108 -0
- package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
- package/dist/cli/commands/collaboration-preflight.js +167 -0
- package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
- package/dist/cli/commands/collaboration-recovery-run.js +224 -0
- package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
- package/dist/cli/commands/collaboration-recovery.js +104 -0
- package/dist/cli/commands/collaboration-resources.d.ts +11 -0
- package/dist/cli/commands/collaboration-resources.js +96 -0
- package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
- package/dist/cli/commands/collaboration-role-plan.js +69 -0
- package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
- package/dist/cli/commands/collaboration-role-run.js +128 -0
- package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
- package/dist/cli/commands/collaboration-rollover.js +103 -0
- package/dist/cli/commands/collaboration-state.d.ts +10 -0
- package/dist/cli/commands/collaboration-state.js +96 -0
- package/dist/cli/commands/collaboration-transition.d.ts +10 -0
- package/dist/cli/commands/collaboration-transition.js +75 -0
- package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
- package/dist/cli/commands/collaboration-work-run.js +178 -0
- package/dist/cli/commands/collaboration.d.ts +4 -0
- package/dist/cli/commands/collaboration.js +39 -4
- package/dist/cli/commands/continuity.js +2 -6
- package/dist/cli/commands/docs.d.ts +2 -0
- package/dist/cli/commands/docs.js +89 -15
- package/dist/cli/commands/host.d.ts +7 -1
- package/dist/cli/commands/host.js +122 -7
- package/dist/cli/commands/init.d.ts +20 -3
- package/dist/cli/commands/init.js +897 -232
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +11 -5
- package/dist/cli/commands/repair.d.ts +12 -0
- package/dist/cli/commands/repair.js +41 -0
- package/dist/cli/commands/runtime.d.ts +1 -1
- package/dist/cli/commands/runtime.js +139 -38
- package/dist/cli/commands/session.d.ts +7 -0
- package/dist/cli/commands/session.js +82 -0
- package/dist/cli/commands/task.js +19 -5
- package/dist/cli/run.js +866 -24
- package/dist/code/identity.d.ts +7 -0
- package/dist/code/identity.js +7 -3
- package/dist/code/registration.js +7 -0
- package/dist/code/service.d.ts +9 -0
- package/dist/code/service.js +16 -3
- package/dist/code/text.js +19 -7
- package/dist/collab/admission.d.ts +91 -66
- package/dist/collab/admission.js +21 -6
- package/dist/collab/canonical.d.ts +1 -1
- package/dist/collab/canonical.js +44 -3
- package/dist/collab/envelope.d.ts +2 -2
- package/dist/collab/envelope.js +12 -12
- package/dist/collab/first-work.d.ts +3 -0
- package/dist/collab/first-work.js +15 -0
- package/dist/collab/handoff.d.ts +16 -0
- package/dist/collab/handoff.js +39 -5
- package/dist/collab/index.d.ts +97 -1
- package/dist/collab/index.js +9 -1
- package/dist/collab/messages.js +126 -3
- package/dist/collab/persisted-messages.d.ts +123 -0
- package/dist/collab/persisted-messages.js +496 -0
- package/dist/collab/preflight.d.ts +1 -1
- package/dist/collab/resources.d.ts +13 -3
- package/dist/collab/resources.js +37 -6
- package/dist/collab/rollover.d.ts +311 -0
- package/dist/collab/rollover.js +1024 -0
- package/dist/collab/store.d.ts +1 -0
- package/dist/collab/store.js +1 -4
- package/dist/collab/types.d.ts +2 -354
- package/dist/collab/validation.d.ts +2 -10
- package/dist/collab/validation.js +2 -303
- package/dist/collab/work-plan.d.ts +105 -0
- package/dist/collab/work-plan.js +843 -0
- package/dist/continuity/context.d.ts +11 -2
- package/dist/continuity/context.js +100 -13
- package/dist/continuity/handoff-bundle.d.ts +296 -0
- package/dist/continuity/handoff-bundle.js +477 -0
- package/dist/continuity/index.d.ts +2 -0
- package/dist/continuity/index.js +1 -0
- package/dist/continuity/paths.js +10 -2
- package/dist/continuity/service.js +91 -15
- package/dist/continuity/task-pin.d.ts +24 -0
- package/dist/continuity/task-pin.js +590 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +6 -0
- package/dist/experimental/index.d.ts +13 -0
- package/dist/experimental/index.js +25 -0
- package/dist/experimental/pi.d.ts +4 -0
- package/dist/experimental/pi.js +12 -0
- package/dist/hosts/claude/native.js +1 -1
- package/dist/hosts/codex/native.js +1 -1
- package/dist/hosts/codex/protocol.js +1 -1
- package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
- package/dist/hosts/integration/bootstrap-config.js +254 -0
- package/dist/hosts/integration/bundle-export.d.ts +44 -0
- package/dist/hosts/integration/bundle-export.js +366 -3
- package/dist/hosts/integration/bundle.js +3 -0
- package/dist/hosts/integration/hook.js +36 -9
- package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
- package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
- package/dist/hosts/integration/pappra-hook.mjs +61 -24749
- package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
- package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
- package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
- package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
- package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
- package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
- package/dist/hosts/integration/pi-native-events.d.ts +112 -0
- package/dist/hosts/integration/pi-native-events.js +348 -0
- package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
- package/dist/hosts/integration/pi-rollover-extension.js +431 -0
- package/dist/hosts/integration/recovery-config.d.ts +48 -0
- package/dist/hosts/integration/recovery-config.js +117 -0
- package/dist/hosts/integration/service.d.ts +6 -1
- package/dist/hosts/integration/service.js +485 -35
- package/dist/hosts/integration/support.d.ts +7 -0
- package/dist/hosts/integration/support.js +9 -1
- package/dist/hosts/integration/templates.d.ts +3 -0
- package/dist/hosts/integration/templates.js +26 -3
- package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
- package/dist/hosts/pi/managed-host-identity.js +40 -0
- package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
- package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
- package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
- package/dist/hosts/pi/managed-sdk-session.js +1639 -0
- package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
- package/dist/hosts/pi/managed-session-extension.js +84 -0
- package/dist/hosts/pi/managed-session.d.ts +177 -0
- package/dist/hosts/pi/managed-session.js +1557 -0
- package/dist/hosts/pi/protocol.js +1 -1
- package/dist/hosts/pi/query-result.js +1 -1
- package/dist/hosts/pi/resume-capability.d.ts +41 -0
- package/dist/hosts/pi/resume-capability.js +33 -0
- package/dist/hosts/pi/rpc.d.ts +182 -134
- package/dist/hosts/pi/rpc.js +230 -37
- package/dist/hosts/pilot.d.ts +97 -74
- package/dist/hosts/pilot.js +5 -1
- package/dist/hosts/version.d.ts +3 -0
- package/dist/hosts/version.js +18 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/bounded-file.d.ts +19 -0
- package/dist/infra/bounded-file.js +66 -0
- package/dist/infra/fs.js +10 -2
- package/dist/infra/git.d.ts +29 -0
- package/dist/infra/git.js +45 -9
- package/dist/infra/json-property.d.ts +2 -0
- package/dist/infra/json-property.js +69 -0
- package/dist/infra/json.d.ts +4 -0
- package/dist/infra/json.js +24 -0
- package/dist/infra/owned-process-group.js +1 -1
- package/dist/infra/private-lock.d.ts +16 -0
- package/dist/infra/private-lock.js +148 -0
- package/dist/infra/private-state.d.ts +7 -0
- package/dist/infra/private-state.js +146 -0
- package/dist/infra/process-identity.d.ts +12 -0
- package/dist/infra/process-identity.js +142 -0
- package/dist/infra/validation.d.ts +5 -0
- package/dist/infra/validation.js +15 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +6 -0
- package/dist/install/apply.d.ts +9 -1
- package/dist/install/apply.js +176 -12
- package/dist/install/dependencies.d.ts +38 -0
- package/dist/install/dependencies.js +282 -0
- package/dist/install/ownership.js +30 -1
- package/dist/install/references.d.ts +31 -0
- package/dist/install/references.js +208 -0
- package/dist/install/repair.d.ts +37 -0
- package/dist/install/repair.js +798 -0
- package/dist/install/reset-local.d.ts +16 -0
- package/dist/install/reset-local.js +304 -0
- package/dist/install/rollback.d.ts +3 -0
- package/dist/install/rollback.js +9 -3
- package/dist/install/runtime.d.ts +10 -6
- package/dist/install/runtime.js +299 -59
- package/dist/install/source-fingerprint.d.ts +2 -0
- package/dist/install/source-fingerprint.js +103 -0
- package/dist/knowledge/catalog.d.ts +1 -0
- package/dist/knowledge/catalog.js +143 -10
- package/dist/knowledge/journal.js +17 -7
- package/dist/knowledge/paths.js +2 -1
- package/dist/knowledge/refs.js +4 -0
- package/dist/knowledge/register-batch.d.ts +4 -0
- package/dist/knowledge/register-batch.js +225 -0
- package/dist/knowledge/relocate.d.ts +5 -0
- package/dist/knowledge/relocate.js +92 -0
- package/dist/knowledge/scan.js +103 -47
- package/dist/knowledge/service.d.ts +3 -0
- package/dist/knowledge/service.js +33 -1
- package/dist/knowledge/write.d.ts +4 -1
- package/dist/knowledge/write.js +1 -1
- package/dist/mcp/server.d.ts +4 -2
- package/dist/mcp/server.js +87 -136
- package/dist/schemas/collaboration-state-validation.d.ts +6 -0
- package/dist/schemas/collaboration-state-validation.js +342 -0
- package/dist/schemas/collaboration-state.d.ts +436 -0
- package/dist/schemas/collaboration-state.js +1 -0
- package/dist/schemas/collaboration.d.ts +335 -0
- package/dist/schemas/collaboration.js +272 -0
- package/dist/schemas/continuity.d.ts +8 -0
- package/dist/schemas/continuity.js +13 -3
- package/dist/schemas/doctor.d.ts +1 -0
- package/dist/schemas/doctor.js +1 -0
- package/dist/schemas/document.d.ts +61 -0
- package/dist/schemas/document.js +9 -0
- package/dist/schemas/errors.d.ts +1 -0
- package/dist/schemas/errors.js +1 -0
- package/dist/schemas/host.d.ts +24 -0
- package/dist/schemas/host.js +19 -0
- package/dist/schemas/install.d.ts +8 -2
- package/dist/schemas/install.js +11 -1
- package/dist/schemas/managed-pi-execution.d.ts +16 -0
- package/dist/schemas/managed-pi-execution.js +2 -0
- package/dist/schemas/outcome.d.ts +9 -5
- package/dist/workspace/config-file.js +1 -3
- package/dist/workspace/documents.js +12 -2
- package/dist/workspace/repositories.d.ts +7 -0
- package/dist/workspace/repositories.js +14 -5
- package/package.json +35 -35
- package/skills/pappra/SKILL.md +28 -7
- package/skills/pappra/references/collaboration.md +633 -0
- package/skills/pappra/references/environment.md +360 -0
- package/skills/pappra/references/hosts.md +20 -0
- package/skills/pappra/references/operations.md +36 -5
- package/dist/app/collab-from.d.ts +0 -35
- package/dist/app/collab-from.js +0 -84
package/dist/install/runtime.js
CHANGED
|
@@ -7,11 +7,14 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
7
7
|
import { getPackageInfo } from '../package-info.js';
|
|
8
8
|
import { createDiagnostic } from '../schemas/diagnostics.js';
|
|
9
9
|
import { diagnosticCodes } from '../schemas/errors.js';
|
|
10
|
-
import { installJournalSchema, runtimeManifestSchema, runtimePackageManifestSchema, runtimeSourceSchema, } from '../schemas/install.js';
|
|
10
|
+
import { installJournalSchema, installLockSchema, runtimeManifestSchema, runtimePackageManifestSchema, runtimeSourceSchema, RUNTIME_MANIFEST_SCHEMA, RUNTIME_DIGEST_SCHEME, } from '../schemas/install.js';
|
|
11
11
|
import { isPathInside } from '../schemas/paths.js';
|
|
12
12
|
import { errorMessage } from '../schemas/outcome.js';
|
|
13
|
+
import { fingerprintSelfSource } from './source-fingerprint.js';
|
|
14
|
+
import { inspectRuntimeReferences } from './references.js';
|
|
13
15
|
import { observePath, readFileBounded, readTextFileBounded, resolveRealpath, sha256Digest, } from '../infra/fs.js';
|
|
14
|
-
import {
|
|
16
|
+
import { materializeInstalledDependencies, readPackageDependencyManifest, } from './dependencies.js';
|
|
17
|
+
import { acquireInstallLock, ensureStatePathBoundary, readJsonDocument, releaseInstallLock, scanJournalDirectory, writeInstallJournal, } from './ownership.js';
|
|
15
18
|
export function resolveUserDataDir(environment = process.env, platform = process.platform, home = homedir()) {
|
|
16
19
|
if (environment['PAPPRA_DATA_HOME'] !== undefined) {
|
|
17
20
|
return resolve(environment['PAPPRA_DATA_HOME']);
|
|
@@ -69,7 +72,7 @@ function isoNow() {
|
|
|
69
72
|
async function hashPackageDirectory(directory) {
|
|
70
73
|
const parts = [];
|
|
71
74
|
const visit = async (current) => {
|
|
72
|
-
const entries = (await readdir(current, { withFileTypes: true })).sort((left, right) => left.name.
|
|
75
|
+
const entries = (await readdir(current, { withFileTypes: true })).sort((left, right) => Buffer.compare(Buffer.from(left.name), Buffer.from(right.name)));
|
|
73
76
|
for (const entry of entries) {
|
|
74
77
|
const path = join(current, entry.name);
|
|
75
78
|
const rel = relative(directory, path).split(sep).join('/');
|
|
@@ -175,6 +178,7 @@ async function packSelfPackage(packageRoot, packageName, version) {
|
|
|
175
178
|
if (manifest.name !== packageName || manifest.version !== version) {
|
|
176
179
|
throw new Error(`Running package ${String(manifest.name)}@${String(manifest.version)} does not match ${packageName}@${version}.`);
|
|
177
180
|
}
|
|
181
|
+
const dependenciesAvailable = (await readPackageDependencyManifest(packageRoot)) !== null;
|
|
178
182
|
const directory = await mkdtemp(join(tmpdir(), 'pappra-self-'));
|
|
179
183
|
// Pack a staging copy: npm refuses to pack reliably from inside a dependency
|
|
180
184
|
// tree (a known "Exit handler never called!" failure on Linux CI).
|
|
@@ -198,6 +202,16 @@ async function packSelfPackage(packageRoot, packageName, version) {
|
|
|
198
202
|
force: true,
|
|
199
203
|
});
|
|
200
204
|
}
|
|
205
|
+
let hasInstalledDependencies = dependenciesAvailable;
|
|
206
|
+
if (hasInstalledDependencies) {
|
|
207
|
+
// Reuse the exact installed dependency closure instead of packing a copy
|
|
208
|
+
// from the registry: payloads are copied without nested node_modules and
|
|
209
|
+
// shared exact versions are hoisted once under the staging root.
|
|
210
|
+
hasInstalledDependencies = (await materializeInstalledDependencies({
|
|
211
|
+
sourceRoot: packageRoot,
|
|
212
|
+
targetRoot: staging,
|
|
213
|
+
})).ok;
|
|
214
|
+
}
|
|
201
215
|
const npm = npmCommand();
|
|
202
216
|
const packed = await runCommand(npm.executable, [
|
|
203
217
|
...npm.args,
|
|
@@ -230,7 +244,13 @@ async function packSelfPackage(packageRoot, packageName, version) {
|
|
|
230
244
|
}
|
|
231
245
|
const tarball = join(directory, filename);
|
|
232
246
|
const bytes = await readFile(tarball);
|
|
233
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
directory,
|
|
249
|
+
packageDirectory: staging,
|
|
250
|
+
dependenciesAvailable: hasInstalledDependencies,
|
|
251
|
+
tarball,
|
|
252
|
+
digest: sha256Digest(bytes),
|
|
253
|
+
};
|
|
234
254
|
}
|
|
235
255
|
async function readPackageManifest(directory, boundaryRoot) {
|
|
236
256
|
const manifestPath = join(directory, 'package.json');
|
|
@@ -256,7 +276,7 @@ function binPath(packageManifest, packageName) {
|
|
|
256
276
|
return packageManifest.bin;
|
|
257
277
|
return (packageManifest.bin[packageName] ?? packageManifest.bin['pappra'] ?? null);
|
|
258
278
|
}
|
|
259
|
-
async function verifyInstalledRuntime(runtimeRoot, expectedVersion, expectedPackageName, timeoutMs) {
|
|
279
|
+
async function verifyInstalledRuntime(runtimeRoot, expectedVersion, expectedPackageName, timeoutMs, verifiedDigest) {
|
|
260
280
|
const packageRoot = join(runtimeRoot, 'node_modules', ...expectedPackageName.split('/'));
|
|
261
281
|
const runtimeReal = await resolveRealpath(runtimeRoot);
|
|
262
282
|
const packageManifest = runtimeReal === null
|
|
@@ -294,7 +314,7 @@ async function verifyInstalledRuntime(runtimeRoot, expectedVersion, expectedPack
|
|
|
294
314
|
}
|
|
295
315
|
return {
|
|
296
316
|
manifest: packageManifest,
|
|
297
|
-
digest: await packageDigest(packageRoot),
|
|
317
|
+
digest: verifiedDigest ?? (await packageDigest(packageRoot)),
|
|
298
318
|
executableRelative: relative(runtimeRoot, executablePath)
|
|
299
319
|
.split(sep)
|
|
300
320
|
.join('/'),
|
|
@@ -309,6 +329,66 @@ async function readRuntimeManifest(runtimeRoot) {
|
|
|
309
329
|
const read = await readJsonDocument(join(runtimeRoot, 'install-manifest.json'), runtimeManifestSchema);
|
|
310
330
|
return read.value;
|
|
311
331
|
}
|
|
332
|
+
/** Recognize an unsupported format without adopting or executing its content. */
|
|
333
|
+
async function incompatibleRuntimeDiagnostic(runtimeRoot) {
|
|
334
|
+
const file = join(runtimeRoot, 'install-manifest.json');
|
|
335
|
+
if ((await observePath(file)).kind !== 'file')
|
|
336
|
+
return null;
|
|
337
|
+
const read = await readTextFileBounded(file);
|
|
338
|
+
if (!read.ok)
|
|
339
|
+
return null;
|
|
340
|
+
let raw;
|
|
341
|
+
try {
|
|
342
|
+
raw = JSON.parse(read.value.text);
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
if (typeof raw !== 'object' ||
|
|
348
|
+
raw === null ||
|
|
349
|
+
!('schema' in raw) ||
|
|
350
|
+
typeof raw.schema !== 'string' ||
|
|
351
|
+
!raw.schema.startsWith('pappra/runtime-manifest/') ||
|
|
352
|
+
(raw.schema === RUNTIME_MANIFEST_SCHEMA &&
|
|
353
|
+
'digestScheme' in raw &&
|
|
354
|
+
raw.digestScheme === RUNTIME_DIGEST_SCHEME))
|
|
355
|
+
return null;
|
|
356
|
+
return createDiagnostic({
|
|
357
|
+
code: diagnosticCodes.runtimeStateIncompatible,
|
|
358
|
+
severity: 'error',
|
|
359
|
+
message: `Unsupported runtime state format at ${file}; this is not a content-corruption diagnosis.`,
|
|
360
|
+
source: file,
|
|
361
|
+
hint: 'This beta does not migrate old state. Use the current package and run pappra init --reconfigure --apply to initialize its runtime; preserve any old data you still need. Nothing was removed.',
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/** Repair verifies published content without executing it or ignoring its journal. */
|
|
365
|
+
export async function verifyRuntimeForRepair(runtimeRoot, version, packageName, installId) {
|
|
366
|
+
const manifest = await readRuntimeManifest(runtimeRoot);
|
|
367
|
+
if (manifest === null ||
|
|
368
|
+
manifest.installId !== installId ||
|
|
369
|
+
manifest.runtimeRoot !== runtimeRoot ||
|
|
370
|
+
manifest.runtimeVersion !== version ||
|
|
371
|
+
manifest.packageName !== packageName ||
|
|
372
|
+
!isPathInside(runtimeRoot, manifest.executable))
|
|
373
|
+
return false;
|
|
374
|
+
const packageRoot = join(runtimeRoot, 'node_modules', ...packageName.split('/'));
|
|
375
|
+
const manifestObservation = await observePath(join(runtimeRoot, 'install-manifest.json'));
|
|
376
|
+
const packageObservation = await observePath(packageRoot);
|
|
377
|
+
if (manifestObservation.kind !== 'file' ||
|
|
378
|
+
packageObservation.kind !== 'directory')
|
|
379
|
+
return false;
|
|
380
|
+
for (const file of [packageRoot, manifest.executable]) {
|
|
381
|
+
if (!(await ensureStatePathBoundary(runtimeRoot, file)).ok)
|
|
382
|
+
return false;
|
|
383
|
+
const real = await resolveRealpath(file);
|
|
384
|
+
if (real === null || !isPathInside(runtimeRoot, real))
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
const executable = await observePath(manifest.executable);
|
|
388
|
+
return (executable.kind === 'file' &&
|
|
389
|
+
(await packageDigest(packageRoot).catch(() => null)) ===
|
|
390
|
+
manifest.packageDigest);
|
|
391
|
+
}
|
|
312
392
|
async function scanRuntimeJournals(version, paths) {
|
|
313
393
|
const directory = await scanJournalDirectory(paths.journalsDir);
|
|
314
394
|
if (directory.kind === 'missing') {
|
|
@@ -340,7 +420,7 @@ async function scanRuntimeJournals(version, paths) {
|
|
|
340
420
|
}
|
|
341
421
|
return { journal, unreadable, directoryUnreadable: false };
|
|
342
422
|
}
|
|
343
|
-
async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
|
|
423
|
+
async function completeExistingRuntime(version, paths, packageName, timeoutMs, expectedSelfFingerprint) {
|
|
344
424
|
const observation = await observePath(paths.runtimeRoot);
|
|
345
425
|
if (observation.kind === 'missing')
|
|
346
426
|
return null;
|
|
@@ -354,6 +434,19 @@ async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
|
|
|
354
434
|
}
|
|
355
435
|
const manifest = await readRuntimeManifest(paths.runtimeRoot);
|
|
356
436
|
if (manifest === null) {
|
|
437
|
+
const incompatible = await incompatibleRuntimeDiagnostic(paths.runtimeRoot);
|
|
438
|
+
if (incompatible !== null) {
|
|
439
|
+
return {
|
|
440
|
+
ok: false,
|
|
441
|
+
status: 'incompatible',
|
|
442
|
+
version,
|
|
443
|
+
runtimeRoot: paths.runtimeRoot,
|
|
444
|
+
manifest: null,
|
|
445
|
+
journal: null,
|
|
446
|
+
diagnostics: [incompatible],
|
|
447
|
+
residualPaths: [],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
357
450
|
return failureResult('corrupt', `Runtime has no valid manifest: ${paths.runtimeRoot}`, {
|
|
358
451
|
version,
|
|
359
452
|
runtimeRoot: paths.runtimeRoot,
|
|
@@ -371,6 +464,15 @@ async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
|
|
|
371
464
|
source: paths.runtimeRoot,
|
|
372
465
|
});
|
|
373
466
|
}
|
|
467
|
+
if (expectedSelfFingerprint !== undefined &&
|
|
468
|
+
manifest.sourceFingerprint !== expectedSelfFingerprint)
|
|
469
|
+
return failureResult('failed', 'This version already contains a different self package payload or resolved dependency identity.', {
|
|
470
|
+
version,
|
|
471
|
+
runtimeRoot: paths.runtimeRoot,
|
|
472
|
+
manifest,
|
|
473
|
+
hint: 'Bump the package version before installing a rebuilt self runtime; an existing version is never silently reused or overwritten for different source bytes.',
|
|
474
|
+
source: paths.runtimeRoot,
|
|
475
|
+
});
|
|
374
476
|
const packageRoot = join(paths.runtimeRoot, 'node_modules', ...packageName.split('/'));
|
|
375
477
|
const digest = await packageDigest(packageRoot).catch(() => null);
|
|
376
478
|
const executableObservation = await observePath(manifest.executable);
|
|
@@ -385,7 +487,10 @@ async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
|
|
|
385
487
|
source: paths.runtimeRoot,
|
|
386
488
|
});
|
|
387
489
|
}
|
|
388
|
-
const verified = await verifyInstalledRuntime(paths.runtimeRoot, version, packageName, timeoutMs
|
|
490
|
+
const verified = await verifyInstalledRuntime(paths.runtimeRoot, version, packageName, timeoutMs,
|
|
491
|
+
// Content was checked before executing the smoke command. Reuse does not
|
|
492
|
+
// publish a new manifest, so hashing it again would discard the result.
|
|
493
|
+
digest);
|
|
389
494
|
if (verified === null) {
|
|
390
495
|
return failureResult('corrupt', `Runtime smoke check failed: ${paths.runtimeRoot}`, {
|
|
391
496
|
version,
|
|
@@ -467,7 +572,42 @@ export async function installRuntime(options) {
|
|
|
467
572
|
source: paths.lockPath,
|
|
468
573
|
});
|
|
469
574
|
}
|
|
470
|
-
|
|
575
|
+
let expectedSelfFingerprint;
|
|
576
|
+
if (options.source.kind === 'self') {
|
|
577
|
+
try {
|
|
578
|
+
const sourceRead = await readTextFileBounded(join(resolve(options.source.path ?? ''), 'package.json'));
|
|
579
|
+
const sourceManifest = sourceRead.ok
|
|
580
|
+
? JSON.parse(sourceRead.value.text)
|
|
581
|
+
: null;
|
|
582
|
+
if (typeof sourceManifest === 'object' &&
|
|
583
|
+
sourceManifest !== null &&
|
|
584
|
+
'name' in sourceManifest &&
|
|
585
|
+
sourceManifest.name === packageName &&
|
|
586
|
+
'version' in sourceManifest &&
|
|
587
|
+
typeof sourceManifest.version === 'string' &&
|
|
588
|
+
sourceManifest.version !== version) {
|
|
589
|
+
// A newer CLI may verify and reuse an older workspace target. It cannot
|
|
590
|
+
// replace that target with its own differently versioned payload.
|
|
591
|
+
const older = await completeExistingRuntime(version, paths, packageName, timeoutMs);
|
|
592
|
+
if (older !== null)
|
|
593
|
+
return older;
|
|
594
|
+
return failureResult('failed', 'The running package version does not match the requested runtime and no verified target is available.', {
|
|
595
|
+
version,
|
|
596
|
+
runtimeRoot: paths.runtimeRoot,
|
|
597
|
+
hint: 'Install the exact workspace runtime version from a versioned package or tarball.',
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
expectedSelfFingerprint = await fingerprintSelfSource(resolve(options.source.path ?? ''), packageName, version);
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
return failureResult('failed', `Cannot identify the running package source: ${errorMessage(error)}`, {
|
|
604
|
+
version,
|
|
605
|
+
runtimeRoot: paths.runtimeRoot,
|
|
606
|
+
hint: 'Review the package payload and resolved dependencies; bump the package version for rebuilt source.',
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const existing = await completeExistingRuntime(version, paths, packageName, timeoutMs, expectedSelfFingerprint);
|
|
471
611
|
if (existing !== null)
|
|
472
612
|
return existing;
|
|
473
613
|
const lock = await acquireInstallLock(paths.lockPath, 'install-runtime', paths.runtimeRoot, dataDir);
|
|
@@ -479,7 +619,7 @@ export async function installRuntime(options) {
|
|
|
479
619
|
source: paths.lockPath,
|
|
480
620
|
});
|
|
481
621
|
}
|
|
482
|
-
const afterLock = await completeExistingRuntime(version, paths, packageName, timeoutMs);
|
|
622
|
+
const afterLock = await completeExistingRuntime(version, paths, packageName, timeoutMs, expectedSelfFingerprint);
|
|
483
623
|
if (afterLock !== null) {
|
|
484
624
|
await releaseInstallLock(paths.lockPath, lock.lock.token);
|
|
485
625
|
return afterLock;
|
|
@@ -505,6 +645,9 @@ export async function installRuntime(options) {
|
|
|
505
645
|
else if (options.source.kind === 'self') {
|
|
506
646
|
const packageRoot = resolve(options.source.path ?? '');
|
|
507
647
|
selfPackage = await packSelfPackage(packageRoot, packageName, version);
|
|
648
|
+
if ((await fingerprintSelfSource(packageRoot, packageName, version)) !==
|
|
649
|
+
expectedSelfFingerprint)
|
|
650
|
+
throw new Error('Self source changed while packing; no runtime will be published. Rebuild with a new version and retry.');
|
|
508
651
|
source = {
|
|
509
652
|
kind: 'self',
|
|
510
653
|
packageName,
|
|
@@ -608,30 +751,37 @@ export async function installRuntime(options) {
|
|
|
608
751
|
if (!(await writeJournal())) {
|
|
609
752
|
throw new Error(`Cannot update install journal: ${journalFile}`);
|
|
610
753
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
'
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
754
|
+
// Reuse installed versions when available so self installs do not depend on npm registry/cache.
|
|
755
|
+
if (options.source.kind === 'self' &&
|
|
756
|
+
selfPackage?.dependenciesAvailable === true) {
|
|
757
|
+
await cp(selfPackage.packageDirectory, join(stage, 'node_modules', ...packageName.split('/')), { recursive: true, dereference: true, force: true });
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
const npm = npmCommand();
|
|
761
|
+
const installArgs = [
|
|
762
|
+
...npm.args,
|
|
763
|
+
'install',
|
|
764
|
+
'--prefix',
|
|
765
|
+
stage,
|
|
766
|
+
'--ignore-scripts',
|
|
767
|
+
'--no-audit',
|
|
768
|
+
'--no-fund',
|
|
769
|
+
'--no-package-lock',
|
|
770
|
+
'--save-exact',
|
|
771
|
+
...(options.offline === true ? ['--offline'] : []),
|
|
772
|
+
source.kind === 'tarball' || source.kind === 'self'
|
|
773
|
+
? source.kind === 'tarball'
|
|
774
|
+
? source.path
|
|
775
|
+
: source.tarball
|
|
776
|
+
: `${source.packageName}@${source.version}`,
|
|
777
|
+
];
|
|
778
|
+
const installed = await runCommand(npm.executable, installArgs, {
|
|
779
|
+
cwd: stage,
|
|
780
|
+
timeoutMs,
|
|
781
|
+
});
|
|
782
|
+
if (installed.timedOut || installed.code !== 0) {
|
|
783
|
+
throw new Error(`Package manager install failed (${installed.timedOut ? 'timeout' : `exit ${String(installed.code)}`}): ${installed.stderr.slice(0, 1000)}`);
|
|
784
|
+
}
|
|
635
785
|
}
|
|
636
786
|
if (selfPackage !== null) {
|
|
637
787
|
await rm(selfPackage.directory, { recursive: true, force: true }).catch(() => undefined);
|
|
@@ -647,7 +797,7 @@ export async function installRuntime(options) {
|
|
|
647
797
|
if (!(await writeJournal()))
|
|
648
798
|
throw new Error('Cannot update install journal.');
|
|
649
799
|
const manifest = {
|
|
650
|
-
schema:
|
|
800
|
+
schema: RUNTIME_MANIFEST_SCHEMA,
|
|
651
801
|
installId,
|
|
652
802
|
packageName,
|
|
653
803
|
runtimeVersion: version,
|
|
@@ -655,6 +805,8 @@ export async function installRuntime(options) {
|
|
|
655
805
|
executable: join(paths.runtimeRoot, ...verification.executableRelative.split('/')),
|
|
656
806
|
source,
|
|
657
807
|
packageDigest: verification.digest,
|
|
808
|
+
digestScheme: RUNTIME_DIGEST_SCHEME,
|
|
809
|
+
sourceFingerprint: await fingerprintSelfSource(join(stage, 'node_modules', ...packageName.split('/')), packageName, version),
|
|
658
810
|
smoke: verification.smoke,
|
|
659
811
|
verifiedAt: isoNow(),
|
|
660
812
|
};
|
|
@@ -744,7 +896,7 @@ async function runtimePackageName(runtimeRoot) {
|
|
|
744
896
|
const read = await readJsonDocument(join(runtimeRoot, 'install-manifest.json'), runtimeManifestSchema);
|
|
745
897
|
return read.value?.packageName ?? getPackageInfo().name;
|
|
746
898
|
}
|
|
747
|
-
export async function inspectRuntimeState(version, dataDir = resolveUserDataDir()) {
|
|
899
|
+
export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(), heldLockToken) {
|
|
748
900
|
const paths = runtimePaths(version, dataDir);
|
|
749
901
|
if (!validVersion(version)) {
|
|
750
902
|
return {
|
|
@@ -803,7 +955,11 @@ export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(
|
|
|
803
955
|
],
|
|
804
956
|
};
|
|
805
957
|
}
|
|
806
|
-
|
|
958
|
+
const ownLock = heldLockToken === undefined
|
|
959
|
+
? null
|
|
960
|
+
: await readJsonDocument(paths.lockPath, installLockSchema);
|
|
961
|
+
if (lockObservation.kind !== 'missing' &&
|
|
962
|
+
(heldLockToken === undefined || ownLock?.value?.token !== heldLockToken)) {
|
|
807
963
|
return {
|
|
808
964
|
version,
|
|
809
965
|
status: 'locked',
|
|
@@ -835,7 +991,11 @@ export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(
|
|
|
835
991
|
}
|
|
836
992
|
return {
|
|
837
993
|
version,
|
|
838
|
-
status: complete.status === 'reused'
|
|
994
|
+
status: complete.status === 'reused'
|
|
995
|
+
? 'complete'
|
|
996
|
+
: complete.status === 'incompatible'
|
|
997
|
+
? 'incompatible'
|
|
998
|
+
: 'corrupt',
|
|
839
999
|
runtimeRoot: paths.runtimeRoot,
|
|
840
1000
|
manifest: complete.manifest,
|
|
841
1001
|
journal: complete.journal,
|
|
@@ -843,6 +1003,66 @@ export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(
|
|
|
843
1003
|
};
|
|
844
1004
|
}
|
|
845
1005
|
export async function removeRuntime(options) {
|
|
1006
|
+
if (!validVersion(options.version))
|
|
1007
|
+
return removeRuntimeAt(options);
|
|
1008
|
+
const dataDir = resolve(options.dataDir ?? resolveUserDataDir());
|
|
1009
|
+
const paths = runtimePaths(options.version, dataDir);
|
|
1010
|
+
const lock = await acquireInstallLock(paths.lockPath, 'remove-runtime', paths.runtimeRoot, dataDir);
|
|
1011
|
+
if (!lock.ok || !lock.lock)
|
|
1012
|
+
return {
|
|
1013
|
+
ok: false,
|
|
1014
|
+
status: 'locked',
|
|
1015
|
+
runtimeRoot: paths.runtimeRoot,
|
|
1016
|
+
residualPaths: [paths.lockPath],
|
|
1017
|
+
diagnostics: [
|
|
1018
|
+
createDiagnostic({
|
|
1019
|
+
code: diagnosticCodes.initApplyResidual,
|
|
1020
|
+
severity: 'error',
|
|
1021
|
+
message: lock.message ?? 'Runtime removal is locked.',
|
|
1022
|
+
source: paths.lockPath,
|
|
1023
|
+
}),
|
|
1024
|
+
],
|
|
1025
|
+
};
|
|
1026
|
+
let result;
|
|
1027
|
+
try {
|
|
1028
|
+
result = await removeRuntimeAt(options, lock.lock.token);
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
result = {
|
|
1032
|
+
ok: false,
|
|
1033
|
+
status: 'failed',
|
|
1034
|
+
runtimeRoot: paths.runtimeRoot,
|
|
1035
|
+
residualPaths: [],
|
|
1036
|
+
diagnostics: [
|
|
1037
|
+
createDiagnostic({
|
|
1038
|
+
code: diagnosticCodes.initApplyFailed,
|
|
1039
|
+
severity: 'error',
|
|
1040
|
+
message: errorMessage(error),
|
|
1041
|
+
source: paths.runtimeRoot,
|
|
1042
|
+
}),
|
|
1043
|
+
],
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
const released = await releaseInstallLock(paths.lockPath, lock.lock.token);
|
|
1047
|
+
return released.ok
|
|
1048
|
+
? result
|
|
1049
|
+
: {
|
|
1050
|
+
...result,
|
|
1051
|
+
ok: false,
|
|
1052
|
+
status: 'failed',
|
|
1053
|
+
residualPaths: [...result.residualPaths, paths.lockPath],
|
|
1054
|
+
diagnostics: [
|
|
1055
|
+
...result.diagnostics,
|
|
1056
|
+
createDiagnostic({
|
|
1057
|
+
code: released.code,
|
|
1058
|
+
severity: 'error',
|
|
1059
|
+
message: released.message,
|
|
1060
|
+
source: paths.lockPath,
|
|
1061
|
+
}),
|
|
1062
|
+
],
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
async function removeRuntimeAt(options, heldLockToken) {
|
|
846
1066
|
const dataDir = resolve(options.dataDir ?? resolveUserDataDir());
|
|
847
1067
|
const paths = runtimePaths(options.version, dataDir);
|
|
848
1068
|
if (!validVersion(options.version)) {
|
|
@@ -862,27 +1082,38 @@ export async function removeRuntime(options) {
|
|
|
862
1082
|
residualPaths: [],
|
|
863
1083
|
};
|
|
864
1084
|
}
|
|
865
|
-
|
|
866
|
-
|
|
1085
|
+
if ((await observePath(paths.runtimeRoot)).kind === 'missing')
|
|
1086
|
+
return {
|
|
1087
|
+
ok: true,
|
|
1088
|
+
status: 'missing',
|
|
1089
|
+
runtimeRoot: paths.runtimeRoot,
|
|
1090
|
+
diagnostics: [],
|
|
1091
|
+
residualPaths: [],
|
|
1092
|
+
};
|
|
1093
|
+
const references = await inspectRuntimeReferences(dataDir, options.version);
|
|
1094
|
+
const activeCount = references.active.length + (options.activeReferences?.length ?? 0);
|
|
1095
|
+
if (activeCount > 0 || options.forceUnknownReferences !== true) {
|
|
867
1096
|
return {
|
|
868
1097
|
ok: false,
|
|
869
|
-
status: 'in-use',
|
|
1098
|
+
status: activeCount > 0 ? 'in-use' : 'unknown',
|
|
870
1099
|
runtimeRoot: paths.runtimeRoot,
|
|
871
1100
|
diagnostics: [
|
|
872
1101
|
createDiagnostic({
|
|
873
1102
|
code: diagnosticCodes.initApplyResidual,
|
|
874
1103
|
severity: 'error',
|
|
875
|
-
message:
|
|
876
|
-
?
|
|
877
|
-
:
|
|
1104
|
+
message: activeCount > 0
|
|
1105
|
+
? `Runtime is referenced by ${activeCount} verified or explicitly reported active workspace(s).`
|
|
1106
|
+
: 'Global reference coverage is unknown: only explicitly registered workspace roots were checked. An empty caller list is not proof of no references.',
|
|
878
1107
|
source: paths.runtimeRoot,
|
|
879
|
-
hint:
|
|
1108
|
+
hint: activeCount > 0
|
|
1109
|
+
? 'Rebind or explicitly retire those workspace bindings; --force never bypasses a verified reference.'
|
|
1110
|
+
: 'Review registered and unregistered workspace usage, then use the explicit CLI --force policy only to accept unknown coverage.',
|
|
880
1111
|
}),
|
|
881
1112
|
],
|
|
882
1113
|
residualPaths: [],
|
|
883
1114
|
};
|
|
884
1115
|
}
|
|
885
|
-
const state = await inspectRuntimeState(options.version, dataDir);
|
|
1116
|
+
const state = await inspectRuntimeState(options.version, dataDir, heldLockToken);
|
|
886
1117
|
if (state.status === 'missing') {
|
|
887
1118
|
return {
|
|
888
1119
|
ok: true,
|
|
@@ -904,7 +1135,7 @@ export async function removeRuntime(options) {
|
|
|
904
1135
|
if (state.status !== 'complete' || state.manifest === null) {
|
|
905
1136
|
return {
|
|
906
1137
|
ok: false,
|
|
907
|
-
status: 'corrupt',
|
|
1138
|
+
status: state.status === 'incompatible' ? 'incompatible' : 'corrupt',
|
|
908
1139
|
runtimeRoot: paths.runtimeRoot,
|
|
909
1140
|
diagnostics: state.diagnostics,
|
|
910
1141
|
residualPaths: [],
|
|
@@ -1098,9 +1329,16 @@ export async function inspectRuntimeStatePassive(version, dataDir = resolveUserD
|
|
|
1098
1329
|
}
|
|
1099
1330
|
const manifestRead = await readJsonDocument(manifestPath, runtimeManifestSchema);
|
|
1100
1331
|
if (!manifestRead.exists || manifestRead.value === null) {
|
|
1332
|
+
const incompatible = manifestRead.exists
|
|
1333
|
+
? await incompatibleRuntimeDiagnostic(paths.runtimeRoot)
|
|
1334
|
+
: null;
|
|
1101
1335
|
return {
|
|
1102
1336
|
version,
|
|
1103
|
-
status:
|
|
1337
|
+
status: incompatible !== null
|
|
1338
|
+
? 'incompatible'
|
|
1339
|
+
: manifestRead.exists
|
|
1340
|
+
? 'unreadable'
|
|
1341
|
+
: 'corrupt',
|
|
1104
1342
|
runtimeRoot: paths.runtimeRoot,
|
|
1105
1343
|
manifest: null,
|
|
1106
1344
|
journal: null,
|
|
@@ -1108,17 +1346,19 @@ export async function inspectRuntimeStatePassive(version, dataDir = resolveUserD
|
|
|
1108
1346
|
lockPresent,
|
|
1109
1347
|
packageDigestMatch: false,
|
|
1110
1348
|
executableExists: false,
|
|
1111
|
-
diagnostics:
|
|
1112
|
-
?
|
|
1113
|
-
:
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1349
|
+
diagnostics: incompatible !== null
|
|
1350
|
+
? [incompatible]
|
|
1351
|
+
: manifestRead.diagnostics.length > 0
|
|
1352
|
+
? manifestRead.diagnostics
|
|
1353
|
+
: [
|
|
1354
|
+
createDiagnostic({
|
|
1355
|
+
code: diagnosticCodes.initApplyFailed,
|
|
1356
|
+
severity: 'error',
|
|
1357
|
+
message: `Runtime manifest is missing: ${paths.runtimeRoot}`,
|
|
1358
|
+
source: paths.runtimeRoot,
|
|
1359
|
+
hint: 'Inspect the partial runtime; doctor does not overwrite it.',
|
|
1360
|
+
}),
|
|
1361
|
+
],
|
|
1122
1362
|
};
|
|
1123
1363
|
}
|
|
1124
1364
|
const manifest = manifestRead.value;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { observePath, readFileBounded, readTextFileBounded, resolveRealpath, sha256Digest, } from '../infra/fs.js';
|
|
4
|
+
import { isPathInside } from '../schemas/paths.js';
|
|
5
|
+
import { readPackageDependencyManifest, resolveInstalledDependency, } from './dependencies.js';
|
|
6
|
+
/** Exactly the payload entries staged by the self-package installer. */
|
|
7
|
+
const SELF_PAYLOAD = [
|
|
8
|
+
'package.json',
|
|
9
|
+
'README.md',
|
|
10
|
+
'LICENSE',
|
|
11
|
+
'THIRD_PARTY_NOTICES.md',
|
|
12
|
+
'dist',
|
|
13
|
+
'assets',
|
|
14
|
+
'skills',
|
|
15
|
+
];
|
|
16
|
+
/** Payload bytes plus actual resolved package-manifest/edge identities; no absolute paths. */
|
|
17
|
+
export async function fingerprintSelfSource(packageRoot, packageName, version) {
|
|
18
|
+
const canonical = await resolveRealpath(packageRoot);
|
|
19
|
+
if (!canonical)
|
|
20
|
+
throw new Error('Running package root cannot be resolved.');
|
|
21
|
+
const parts = [];
|
|
22
|
+
let files = 0;
|
|
23
|
+
let bytes = 0;
|
|
24
|
+
const active = new Set();
|
|
25
|
+
const visit = async (relative) => {
|
|
26
|
+
const path = join(canonical, relative);
|
|
27
|
+
const presence = await observePath(path);
|
|
28
|
+
if (presence.kind === 'missing') {
|
|
29
|
+
parts.push(`missing:${relative}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const real = await resolveRealpath(path);
|
|
33
|
+
if (!real || !isPathInside(canonical, real))
|
|
34
|
+
throw new Error(`Self payload escapes its package root: ${relative}`);
|
|
35
|
+
const target = await observePath(real);
|
|
36
|
+
if (target.kind === 'directory') {
|
|
37
|
+
if (active.has(real))
|
|
38
|
+
throw new Error('Self payload contains a directory cycle.');
|
|
39
|
+
active.add(real);
|
|
40
|
+
parts.push(`dir:${relative}`);
|
|
41
|
+
for (const entry of (await readdir(real)).sort())
|
|
42
|
+
await visit(`${relative}/${entry}`);
|
|
43
|
+
active.delete(real);
|
|
44
|
+
}
|
|
45
|
+
else if (target.kind === 'file') {
|
|
46
|
+
if (++files > 8192)
|
|
47
|
+
throw new Error('Self payload exceeds the fingerprint file budget.');
|
|
48
|
+
const read = await readFileBounded(real, 64 * 1024 * 1024);
|
|
49
|
+
if (!read.ok)
|
|
50
|
+
throw new Error(`Cannot fingerprint self payload: ${relative}`);
|
|
51
|
+
bytes += read.value.size;
|
|
52
|
+
if (bytes > 256 * 1024 * 1024)
|
|
53
|
+
throw new Error('Self payload exceeds the fingerprint byte budget.');
|
|
54
|
+
parts.push(`file:${relative}:${read.value.digest}`);
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
throw new Error(`Unsupported self payload entry: ${relative}`);
|
|
58
|
+
};
|
|
59
|
+
const manifest = await readTextFileBounded(join(canonical, 'package.json'));
|
|
60
|
+
if (!manifest.ok)
|
|
61
|
+
throw new Error('Running package manifest is unreadable.');
|
|
62
|
+
const decoded = JSON.parse(manifest.value.text);
|
|
63
|
+
if (decoded.name !== packageName || decoded.version !== version)
|
|
64
|
+
throw new Error('Running package name/version differs from the requested runtime; bump the package version before reinstalling.');
|
|
65
|
+
for (const entry of SELF_PAYLOAD)
|
|
66
|
+
await visit(entry);
|
|
67
|
+
const visited = new Set();
|
|
68
|
+
const graph = new Set();
|
|
69
|
+
const dependencies = async (directory) => {
|
|
70
|
+
const real = await resolveRealpath(directory);
|
|
71
|
+
if (!real || visited.has(real))
|
|
72
|
+
return;
|
|
73
|
+
visited.add(real);
|
|
74
|
+
if (visited.size > 512)
|
|
75
|
+
throw new Error('Resolved dependency identity budget exceeded.');
|
|
76
|
+
const read = await readTextFileBounded(join(real, 'package.json'));
|
|
77
|
+
const declared = await readPackageDependencyManifest(real);
|
|
78
|
+
if (!read.ok || !declared)
|
|
79
|
+
throw new Error('Cannot read a resolved dependency manifest.');
|
|
80
|
+
const value = JSON.parse(read.value.text);
|
|
81
|
+
const edges = [];
|
|
82
|
+
for (const [kind, names] of [
|
|
83
|
+
['required', declared.required],
|
|
84
|
+
['optional', declared.optional],
|
|
85
|
+
]) {
|
|
86
|
+
for (const name of [...names].sort()) {
|
|
87
|
+
const resolved = await resolveInstalledDependency(real, name);
|
|
88
|
+
if (!resolved) {
|
|
89
|
+
edges.push(`${kind}:${name}:missing`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const resolvedManifest = await readTextFileBounded(join(resolved, 'package.json'));
|
|
93
|
+
if (!resolvedManifest.ok)
|
|
94
|
+
throw new Error('Cannot read a resolved dependency identity.');
|
|
95
|
+
edges.push(`${kind}:${name}:${resolvedManifest.value.digest}`);
|
|
96
|
+
await dependencies(resolved);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
graph.add(`${String(value.name)}@${String(value.version)}:${read.value.digest}:${edges.join('|')}`);
|
|
100
|
+
};
|
|
101
|
+
await dependencies(canonical);
|
|
102
|
+
return sha256Digest([...parts, ...[...graph].sort().map((entry) => `dependency:${entry}`)].join('\n'));
|
|
103
|
+
}
|
|
@@ -13,6 +13,7 @@ export interface CatalogFileState {
|
|
|
13
13
|
readonly catalog: DocumentCatalog | null;
|
|
14
14
|
readonly diagnostics: readonly Diagnostic[];
|
|
15
15
|
readonly valid: boolean;
|
|
16
|
+
readonly recordDigests?: ReadonlyMap<string, string>;
|
|
16
17
|
}
|
|
17
18
|
export interface KnowledgeCatalogs {
|
|
18
19
|
readonly paths: KnowledgePaths;
|