firedrill-run 2.2.4 → 3.0.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 +178 -22
- package/bin/firedrill.mjs +29 -5
- package/dist/assembly.json +13 -0
- package/dist/cli/runtime/candidate_copy.py +395 -0
- package/dist/cli/runtime/source_snapshot.py +391 -0
- package/dist/cli/src/administration.js +295 -0
- package/dist/cli/src/agent-changes.js +496 -0
- package/dist/cli/src/assessment-requests.js +67 -0
- package/dist/cli/src/auth.js +465 -0
- package/dist/cli/src/billing-commerce-contracts.js +1 -0
- package/dist/cli/src/billing-usage.js +227 -0
- package/dist/cli/src/billing-wallet-contracts.js +1 -0
- package/dist/cli/src/billing.js +170 -0
- package/dist/cli/src/bootstrap-process-lock.js +136 -0
- package/dist/cli/src/browser-links.js +14 -0
- package/dist/cli/src/campaign-controls.js +122 -0
- package/dist/cli/src/campaign-diagnoses.js +118 -0
- package/dist/cli/src/campaign-reports.js +1215 -0
- package/dist/cli/src/campaign-world.js +156 -0
- package/dist/cli/src/candidate-copy.js +376 -0
- package/dist/cli/src/catalog-contracts.js +1 -0
- package/dist/cli/src/ci-access-contracts.js +1 -0
- package/dist/cli/src/ci-configuration-contracts.js +1 -0
- package/dist/cli/src/ci-coverage.js +64 -0
- package/dist/cli/src/ci-delivery-contracts.js +1 -0
- package/dist/cli/src/ci-result-contracts.js +1 -0
- package/dist/cli/src/ci-run-contracts.js +1 -0
- package/dist/cli/src/ci-setup-proposals.js +52 -0
- package/dist/cli/src/company-policies.js +229 -0
- package/dist/cli/src/completion.js +79 -0
- package/dist/cli/src/configuration-intents.js +277 -0
- package/dist/cli/src/configuration.js +369 -0
- package/dist/cli/src/continuous-simulation.js +277 -0
- package/dist/cli/src/coordinated-checkpoints.js +133 -0
- package/dist/cli/src/credential-mode.js +139 -0
- package/dist/cli/src/credentials.js +331 -0
- package/dist/cli/src/credit-format.js +8 -0
- package/dist/cli/src/dataset-contracts.js +1 -0
- package/dist/cli/src/dataset-export.js +154 -0
- package/dist/cli/src/dataset-reads.js +37 -0
- package/dist/cli/src/dependency-preparation.js +239 -0
- package/dist/cli/src/director-tool-setup.js +104 -0
- package/dist/cli/src/driver-review.js +225 -0
- package/dist/cli/src/evaluation-authoring.js +315 -0
- package/dist/cli/src/evidence-bundles.js +367 -0
- package/dist/cli/src/execution-proposals.js +36 -0
- package/dist/cli/src/http.js +302 -0
- package/dist/cli/src/local-container-boundary.js +38 -0
- package/dist/cli/src/local-container-policy.js +201 -0
- package/dist/cli/src/local-execution-setup.js +81 -0
- package/dist/cli/src/local-source-basis.js +73 -0
- package/dist/cli/src/local-source.js +66 -0
- package/dist/cli/src/local-value-store.js +91 -0
- package/dist/cli/src/local-values.js +163 -0
- package/dist/cli/src/login-organizations.js +70 -0
- package/dist/cli/src/main.js +1622 -0
- package/dist/cli/src/mcp-connection.js +109 -0
- package/dist/cli/src/native/bindings.js +137 -0
- package/dist/cli/src/native/canonical.js +370 -0
- package/dist/cli/src/native/cloud-resources.js +942 -0
- package/dist/cli/src/native/companion-channel.js +421 -0
- package/dist/cli/src/native/companion-ipc.js +473 -0
- package/dist/cli/src/native/contracts.js +218 -0
- package/dist/cli/src/native/control-plane.js +1354 -0
- package/dist/cli/src/native/credit-approval.js +15 -0
- package/dist/cli/src/native/dependency-proxy.js +1006 -0
- package/dist/cli/src/native/drill-cleanup.js +115 -0
- package/dist/cli/src/native/drill-runner.js +1562 -0
- package/dist/cli/src/native/drivers/action-trace.js +210 -0
- package/dist/cli/src/native/drivers/function-wrapper.js +152 -0
- package/dist/cli/src/native/drivers/http.js +442 -0
- package/dist/cli/src/native/drivers/index.js +47 -0
- package/dist/cli/src/native/drivers/jsonl-process.js +538 -0
- package/dist/cli/src/native/drivers/playwright.js +600 -0
- package/dist/cli/src/native/drivers/signed-event.js +244 -0
- package/dist/cli/src/native/drivers/types.js +578 -0
- package/dist/cli/src/native/evidence-filter.js +255 -0
- package/dist/cli/src/native/isolated-access.js +241 -0
- package/dist/cli/src/native/library-sharing.js +220 -0
- package/dist/cli/src/native/local-run.js +667 -0
- package/dist/cli/src/native/local-secrets.js +19 -0
- package/dist/cli/src/native/mcp-config.js +123 -0
- package/dist/cli/src/native/metadata-requests.js +120 -0
- package/dist/cli/src/native/observations.js +1159 -0
- package/dist/cli/src/native/owned-groups.js +46 -0
- package/dist/cli/src/native/plan.js +594 -0
- package/dist/cli/src/native/png.js +159 -0
- package/dist/cli/src/native/ports.js +43 -0
- package/dist/cli/src/native/readiness.js +139 -0
- package/dist/cli/src/native/reassessments.js +206 -0
- package/dist/cli/src/native/redaction.js +235 -0
- package/dist/cli/src/native/registration.js +155 -0
- package/dist/cli/src/native/roots.js +110 -0
- package/dist/cli/src/native/run-cleanup.js +199 -0
- package/dist/cli/src/native/scratch.js +117 -0
- package/dist/cli/src/native/screenshots.js +60 -0
- package/dist/cli/src/native/session.js +556 -0
- package/dist/cli/src/native/standalone-proxy.js +580 -0
- package/dist/cli/src/native/standalone-renewals.js +183 -0
- package/dist/cli/src/native/standalone-state.js +193 -0
- package/dist/cli/src/native/standalone-tools.js +187 -0
- package/dist/cli/src/native/supervisor.js +831 -0
- package/dist/cli/src/native/target-auth.js +161 -0
- package/dist/cli/src/native/target-state.js +330 -0
- package/dist/cli/src/native/tool-reuse.js +364 -0
- package/dist/cli/src/native/tool-setups.js +336 -0
- package/dist/cli/src/native/world-reuse.js +288 -0
- package/dist/cli/src/onboarding.js +23 -0
- package/dist/cli/src/priority-requests.js +70 -0
- package/dist/cli/src/priority.js +223 -0
- package/dist/cli/src/prompts.js +184 -0
- package/dist/cli/src/real-operations.js +221 -0
- package/dist/cli/src/recipe-intents.js +58 -0
- package/dist/cli/src/recorded-context-intents.js +59 -0
- package/dist/cli/src/recorded-context.js +248 -0
- package/dist/cli/src/recordings.js +69 -0
- package/dist/cli/src/registration-guide.js +39 -0
- package/dist/cli/src/report-exports.js +213 -0
- package/dist/cli/src/request-input.js +47 -0
- package/dist/cli/src/resource-lifecycle.js +141 -0
- package/dist/cli/src/revision-requests.js +55 -0
- package/dist/cli/src/runner-abandon.js +53 -0
- package/dist/cli/src/runner-campaign-controls.js +262 -0
- package/dist/cli/src/runner-claim-reads.js +36 -0
- package/dist/cli/src/runner-completion.js +65 -0
- package/dist/cli/src/runner-configuration.js +546 -0
- package/dist/cli/src/runner-container.js +794 -0
- package/dist/cli/src/runner-evidence-erasure.js +351 -0
- package/dist/cli/src/runner-evidence-privacy.js +37 -0
- package/dist/cli/src/runner-evidence-retention.js +161 -0
- package/dist/cli/src/runner-evidence.js +326 -0
- package/dist/cli/src/runner-execute.js +92 -0
- package/dist/cli/src/runner-execution-deadline.js +25 -0
- package/dist/cli/src/runner-grants.js +307 -0
- package/dist/cli/src/runner-identity.js +615 -0
- package/dist/cli/src/runner-inputs.js +308 -0
- package/dist/cli/src/runner-jobs.js +49 -0
- package/dist/cli/src/runner-model-transport.js +392 -0
- package/dist/cli/src/runner-ownership.js +332 -0
- package/dist/cli/src/runner-process-lock.js +175 -0
- package/dist/cli/src/runner-runtime.js +396 -0
- package/dist/cli/src/runner-session-channel.js +377 -0
- package/dist/cli/src/runner-trial-completion.js +34 -0
- package/dist/cli/src/runner-trial-journal.js +358 -0
- package/dist/cli/src/runner-trial-live.js +393 -0
- package/dist/cli/src/runner-trial-protocol.js +289 -0
- package/dist/cli/src/runner-trial-sync.js +110 -0
- package/dist/cli/src/runner-trial-transfer.js +107 -0
- package/dist/cli/src/runner-voice.js +40 -0
- package/dist/cli/src/runner-world-claim.js +21 -0
- package/dist/cli/src/runner-world-collector.js +159 -0
- package/dist/cli/src/runner-world-command-evidence.js +487 -0
- package/dist/cli/src/runner-world-commands.js +268 -0
- package/dist/cli/src/runner-world-completion.js +76 -0
- package/dist/cli/src/runner-world-erasure.js +95 -0
- package/dist/cli/src/runner-world-evidence-sync.js +91 -0
- package/dist/cli/src/runner-world-journal.js +510 -0
- package/dist/cli/src/runner-world-native-filters.js +134 -0
- package/dist/cli/src/runner-world-observations.js +50 -0
- package/dist/cli/src/runner-world-session-protocol.js +134 -0
- package/dist/cli/src/saved-records.js +40 -0
- package/dist/cli/src/scenario-authoring.js +212 -0
- package/dist/cli/src/scenario-discovery.js +216 -0
- package/dist/cli/src/scenario-paths.js +68 -0
- package/dist/cli/src/scenario-requests.js +4 -0
- package/dist/cli/src/service-receipts.js +105 -0
- package/dist/cli/src/simple/authoring.js +270 -0
- package/dist/cli/src/simple/credit-approval.js +16 -0
- package/dist/cli/src/simple/help.js +98 -0
- package/dist/cli/src/simple/init.js +61 -0
- package/dist/cli/src/simple/library-sharing.js +414 -0
- package/dist/cli/src/simple/metadata-requests.js +46 -0
- package/dist/cli/src/simple/native-report.js +841 -0
- package/dist/cli/src/simple/native-tools.js +780 -0
- package/dist/cli/src/simple/plan-apply.js +506 -0
- package/dist/cli/src/simple/reassessment.js +323 -0
- package/dist/cli/src/simple/register.js +123 -0
- package/dist/cli/src/simple/run.js +122 -0
- package/dist/cli/src/simple/status.js +49 -0
- package/dist/cli/src/simple/tool-renewal.js +308 -0
- package/dist/cli/src/simple/tool-reuse.js +526 -0
- package/dist/cli/src/simple/tool-start-catalog.js +100 -0
- package/dist/cli/src/simple/tool-state.js +426 -0
- package/dist/cli/src/simple/tools-author.js +222 -0
- package/dist/cli/src/simple/watch.js +28 -0
- package/dist/cli/src/simple/world-reuse.js +586 -0
- package/dist/cli/src/simulation-intents.js +406 -0
- package/dist/cli/src/simulation-run.js +1058 -0
- package/dist/cli/src/source-batch.js +322 -0
- package/dist/cli/src/source-connection.js +451 -0
- package/dist/cli/src/source-runtime.js +346 -0
- package/dist/cli/src/source-selection-intents.js +75 -0
- package/dist/cli/src/source-sets.js +214 -0
- package/dist/cli/src/target-models.js +109 -0
- package/dist/cli/src/tool-catalog.js +224 -0
- package/dist/cli/src/tool-command-contracts.js +1 -0
- package/dist/cli/src/tool-command-export.js +245 -0
- package/dist/cli/src/tool-connection-contracts.js +1 -0
- package/dist/cli/src/tool-connection-reads.js +23 -0
- package/dist/cli/src/tool-contributions.js +58 -0
- package/dist/cli/src/tool-data.js +116 -0
- package/dist/cli/src/tool-datasets.js +176 -0
- package/dist/cli/src/tool-group-contracts.js +1 -0
- package/dist/cli/src/tool-group-reads.js +26 -0
- package/dist/cli/src/tool-group-state.js +147 -0
- package/dist/cli/src/tool-groups.js +311 -0
- package/dist/cli/src/tool-instances.js +186 -0
- package/dist/cli/src/tool-lifecycle-contracts.js +1 -0
- package/dist/cli/src/tool-lifecycle-reads.js +30 -0
- package/dist/cli/src/tool-lifecycle.js +323 -0
- package/dist/cli/src/tool-package-contracts.js +1 -0
- package/dist/cli/src/tool-package-reads.js +41 -0
- package/dist/cli/src/tool-records.js +53 -0
- package/dist/cli/src/tool-runtime.js +96 -0
- package/dist/cli/src/tool-state-contracts.js +1 -0
- package/dist/cli/src/tool-state-reads.js +31 -0
- package/dist/cli/src/tools.js +306 -0
- package/dist/cli/src/trial-actions.js +93 -0
- package/dist/cli/src/trial-checkpoints.js +133 -0
- package/dist/cli/src/trial-checks.js +70 -0
- package/dist/cli/src/trial-state.js +197 -0
- package/dist/cli/src/vault.js +266 -0
- package/dist/cli/src/workspace-registration.js +383 -0
- package/dist/cli/src/workspace-team-contracts.js +1 -0
- package/dist/cli/src/world-access.js +231 -0
- package/dist/cli/src/world-agent.js +4 -0
- package/dist/cli/src/world-checkpoint-scopes.js +15 -0
- package/dist/cli/src/world-contracts.js +6 -0
- package/dist/cli/src/world-creation.js +201 -0
- package/dist/cli/src/world-datasets.js +207 -0
- package/dist/cli/src/world-inspection.js +257 -0
- package/dist/cli/src/world-recipes.js +103 -0
- package/dist/cli/src/world-requests.js +241 -0
- package/dist/cli/src/world-session-checkpoints.js +196 -0
- package/dist/cli/src/world-session-lifecycle.js +207 -0
- package/dist/cli/src/world-session-requests.js +215 -0
- package/dist/cli/src/world-session-state.js +145 -0
- package/dist/cli/src/world-session-voice.js +66 -0
- package/dist/cli/src/world-sessions.js +288 -0
- package/dist/cli/src/world-stream.js +99 -0
- package/dist/cli/src/world-time-report.js +58 -0
- package/dist/contracts/agent-change-contracts.js +75 -0
- package/dist/contracts/agent-execution-contracts.js +38 -0
- package/dist/contracts/application-state-contracts.js +26 -0
- package/dist/contracts/assessment-aggregate-contracts.js +15 -0
- package/dist/contracts/assessment-contracts.js +74 -0
- package/dist/contracts/billing-commerce-contracts.js +107 -0
- package/dist/contracts/billing-wallet-contracts.js +43 -0
- package/dist/contracts/campaign-lifecycle.js +1 -0
- package/dist/contracts/campaign-world-contracts.js +80 -0
- package/dist/contracts/catalog-contracts.js +73 -0
- package/dist/contracts/ci-access-contracts.js +68 -0
- package/dist/contracts/ci-configuration-contracts.js +88 -0
- package/dist/contracts/ci-coverage-contracts.js +65 -0
- package/dist/contracts/ci-coverage-labels.js +25 -0
- package/dist/contracts/ci-delivery-contracts.js +61 -0
- package/dist/contracts/ci-result-contracts.js +103 -0
- package/dist/contracts/ci-run-contracts.js +51 -0
- package/dist/contracts/ci-setup-contracts.js +88 -0
- package/dist/contracts/clip-contracts.js +32 -0
- package/dist/contracts/comparison-contracts.js +53 -0
- package/dist/contracts/composite-contracts.js +34 -0
- package/dist/contracts/context-contracts.js +76 -0
- package/dist/contracts/continuation-report-contracts.js +1 -0
- package/dist/contracts/continuity-requirements.js +8 -0
- package/dist/contracts/coordinated-checkpoint-contracts.js +34 -0
- package/dist/contracts/dataset-contracts.js +88 -0
- package/dist/contracts/dependency-decision-contracts.js +36 -0
- package/dist/contracts/dependency-protocol-contracts.js +43 -0
- package/dist/contracts/director-execution.js +51 -0
- package/dist/contracts/driver-review-contracts.js +99 -0
- package/dist/contracts/evidence-contracts.js +65 -0
- package/dist/contracts/evidence-coverage-contracts.js +10 -0
- package/dist/contracts/export-contracts.js +44 -0
- package/dist/contracts/github-directory-contracts.js +96 -0
- package/dist/contracts/insight-contracts.js +11 -0
- package/dist/contracts/inspection-contracts.js +1 -0
- package/dist/contracts/local-claim-contracts.js +39 -0
- package/dist/contracts/local-configuration-contracts.js +7 -0
- package/dist/contracts/local-evidence-contracts.js +32 -0
- package/dist/contracts/local-grant-contracts.js +83 -0
- package/dist/contracts/local-input-contracts.js +19 -0
- package/dist/contracts/local-runner-contracts.js +42 -0
- package/dist/contracts/local-trial-live-contracts.js +16 -0
- package/dist/contracts/local-value-source-contracts.js +92 -0
- package/dist/contracts/local-world-native-contracts.js +8 -0
- package/dist/contracts/mcp-contracts.js +42 -0
- package/dist/contracts/media-contracts.js +28 -0
- package/dist/contracts/native-check-contracts.js +31 -0
- package/dist/contracts/native-identity-contracts.js +52 -0
- package/dist/contracts/onboarding-contracts.js +32 -0
- package/dist/contracts/performance-contracts.js +85 -0
- package/dist/contracts/planning-contracts.js +81 -0
- package/dist/contracts/real-operation-contracts.js +30 -0
- package/dist/contracts/recommendation-contracts.js +54 -0
- package/dist/contracts/report-contracts.js +1 -0
- package/dist/contracts/resource-lifecycle-contracts.js +57 -0
- package/dist/contracts/revision-contracts.js +12 -0
- package/dist/contracts/revision-request-contracts.js +39 -0
- package/dist/contracts/saved-record-contracts.js +76 -0
- package/dist/contracts/scenario-draft-comparison.js +91 -0
- package/dist/contracts/scenario-world-contracts.js +14 -0
- package/dist/contracts/service-contracts.js +1 -0
- package/dist/contracts/session-command-contracts.js +56 -0
- package/dist/contracts/session-contracts.js +65 -0
- package/dist/contracts/session-state-contracts.js +67 -0
- package/dist/contracts/session-voice-contracts.js +45 -0
- package/dist/contracts/source-set-contracts.js +26 -0
- package/dist/contracts/source-upload-contracts.js +22 -0
- package/dist/contracts/tool-command-contracts.js +57 -0
- package/dist/contracts/tool-connection-contracts.js +87 -0
- package/dist/contracts/tool-contribution-contracts.js +35 -0
- package/dist/contracts/tool-group-contracts.js +53 -0
- package/dist/contracts/tool-group-state-contracts.js +63 -0
- package/dist/contracts/tool-lifecycle-contracts.js +94 -0
- package/dist/contracts/tool-package-contracts.js +91 -0
- package/dist/contracts/tool-protocol-contracts.js +71 -0
- package/dist/contracts/tool-runtime-billing-contracts.js +28 -0
- package/dist/contracts/tool-setup-proposal.js +59 -0
- package/dist/contracts/tool-state-contracts.js +79 -0
- package/dist/contracts/trial-state-contracts.js +30 -0
- package/dist/contracts/typescript/agent-change.js +1 -0
- package/dist/contracts/typescript/assessment-aggregate.js +1 -0
- package/dist/contracts/typescript/assessment.js +1 -0
- package/dist/contracts/typescript/billing-commerce.js +1 -0
- package/dist/contracts/typescript/campaigns.js +1 -0
- package/dist/contracts/typescript/catalog.js +1 -0
- package/dist/contracts/typescript/ci-access.js +1 -0
- package/dist/contracts/typescript/ci-configuration.js +1 -0
- package/dist/contracts/typescript/ci-delivery.js +1 -0
- package/dist/contracts/typescript/ci-result.js +1 -0
- package/dist/contracts/typescript/ci-runs.js +1 -0
- package/dist/contracts/typescript/clips.js +1 -0
- package/dist/contracts/typescript/comparison.js +1 -0
- package/dist/contracts/typescript/composite.js +1 -0
- package/dist/contracts/typescript/connections.js +1 -0
- package/dist/contracts/typescript/context.js +1 -0
- package/dist/contracts/typescript/datasets.js +1 -0
- package/dist/contracts/typescript/evidence-coverage.js +1 -0
- package/dist/contracts/typescript/export.js +1 -0
- package/dist/contracts/typescript/github-directory.js +1 -0
- package/dist/contracts/typescript/insight.js +1 -0
- package/dist/contracts/typescript/inspection.js +1 -0
- package/dist/contracts/typescript/intake.js +1 -0
- package/dist/contracts/typescript/local-claim.js +1 -0
- package/dist/contracts/typescript/local-configuration.js +1 -0
- package/dist/contracts/typescript/local-evidence.js +1 -0
- package/dist/contracts/typescript/local-grant.js +1 -0
- package/dist/contracts/typescript/local-input.js +1 -0
- package/dist/contracts/typescript/local-runner.js +1 -0
- package/dist/contracts/typescript/local-trial-live.js +1 -0
- package/dist/contracts/typescript/local-value-source.js +1 -0
- package/dist/contracts/typescript/local-world-native.js +1 -0
- package/dist/contracts/typescript/mcp.js +1 -0
- package/dist/contracts/typescript/media.js +1 -0
- package/dist/contracts/typescript/native-check.js +1 -0
- package/dist/contracts/typescript/native-identity.js +1 -0
- package/dist/contracts/typescript/onboarding.js +1 -0
- package/dist/contracts/typescript/performance.js +1 -0
- package/dist/contracts/typescript/planning.js +1 -0
- package/dist/contracts/typescript/preparation.js +1 -0
- package/dist/contracts/typescript/real-operation.js +1 -0
- package/dist/contracts/typescript/recommendation.js +1 -0
- package/dist/contracts/typescript/reports.js +1 -0
- package/dist/contracts/typescript/resource-lifecycle.js +1 -0
- package/dist/contracts/typescript/saved-record.js +1 -0
- package/dist/contracts/typescript/scenarios.js +1 -0
- package/dist/contracts/typescript/services.js +1 -0
- package/dist/contracts/typescript/session-command.js +1 -0
- package/dist/contracts/typescript/session-state.js +1 -0
- package/dist/contracts/typescript/session-voice.js +1 -0
- package/dist/contracts/typescript/session.js +1 -0
- package/dist/contracts/typescript/tool-command.js +1 -0
- package/dist/contracts/typescript/tool-connection.js +1 -0
- package/dist/contracts/typescript/tool-contribution.js +1 -0
- package/dist/contracts/typescript/tool-groups.js +1 -0
- package/dist/contracts/typescript/tool-lifecycle.js +1 -0
- package/dist/contracts/typescript/tool-package.js +1 -0
- package/dist/contracts/typescript/tool-state.js +1 -0
- package/dist/contracts/typescript/trial-state.js +1 -0
- package/dist/contracts/typescript/vault.js +1 -0
- package/dist/contracts/typescript/workspace-team.js +1 -0
- package/dist/contracts/typescript/world-stream.js +1 -0
- package/dist/contracts/workspace-team-contracts.js +55 -0
- package/dist/contracts/world-command.js +65 -0
- package/dist/contracts/world-lifetime-contracts.js +74 -0
- package/dist/contracts/world-recipe-contracts.js +27 -0
- package/dist/contracts/world-stream-contracts.js +27 -0
- package/dist/package.json +1 -0
- package/package.json +16 -16
- package/LICENSE.md +0 -23
- package/PACKAGING.json +0 -50
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, mkdir, open, rename, unlink } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
6
|
+
import { api } from './auth.js';
|
|
7
|
+
import { readLocalClaim } from './runner-claim-reads.js';
|
|
8
|
+
import { localInputPacketMatches, localInputPendingMatches } from '../../contracts/local-input-contracts.js';
|
|
9
|
+
import { privateDirectory } from './credentials.js';
|
|
10
|
+
import { CliError } from './http.js';
|
|
11
|
+
import { signRunnerRequest } from './runner-identity.js';
|
|
12
|
+
import { identifier } from './world-contracts.js';
|
|
13
|
+
import { processLifetimeLock } from './runner-process-lock.js';
|
|
14
|
+
const CHUNK = 256 * 1024;
|
|
15
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
16
|
+
const IMAGE = /^[a-z][a-z0-9-]{0,62}-docker\.pkg\.dev\/[a-z][a-z0-9-]{4,62}\/[a-z][a-z0-9-]{0,62}\/[a-z][a-z0-9-]{0,62}@sha256:[a-f0-9]{64}$/;
|
|
17
|
+
const FILES = { source: 'source.zip', candidate: 'candidates.zip', compiled: 'validation.zip', scenarios: 'scenarios.json', packages: 'packages.zip', dataset: 'world-dataset.json', checkpoint: 'initial-checkpoint.json', application_checkpoint: 'initial-application-checkpoint.json' };
|
|
18
|
+
const MAXIMUM = { source: 128 * 1024 ** 2, candidate: 128 * 1024 ** 2, compiled: 128 * 1024 ** 2, scenarios: 64 * 1024 ** 2,
|
|
19
|
+
packages: 64 * 1024 ** 3, dataset: 64 * 1024 ** 2, checkpoint: 512 * 1024 ** 2, application_checkpoint: 8 * 1024 ** 2 }; // Streaming file guard, not a customer simulation quota.
|
|
20
|
+
function canonical(value) {
|
|
21
|
+
if (Array.isArray(value))
|
|
22
|
+
return '[' + value.map(canonical).join(',') + ']';
|
|
23
|
+
if (value && typeof value === 'object') {
|
|
24
|
+
const row = value;
|
|
25
|
+
return '{' + Object.keys(row).sort().map(key => JSON.stringify(key) + ':' + canonical(row[key])).join(',') + '}';
|
|
26
|
+
}
|
|
27
|
+
const body = JSON.stringify(value);
|
|
28
|
+
if (body === undefined)
|
|
29
|
+
throw new CliError('local_input_invalid', 'The input manifest is incomplete.');
|
|
30
|
+
return body;
|
|
31
|
+
}
|
|
32
|
+
const sha = (value) => createHash('sha256').update(value).digest('hex');
|
|
33
|
+
const invalid = () => new CliError('local_input_identity_changed', 'The execution inputs do not match their retained identities. No agent was started.');
|
|
34
|
+
const prefix = (job) => `/api/workspaces/${identifier(job.workspace)}/local-runners/${identifier(job.device)}/jobs/${identifier(job.job)}/claims/${identifier(job.request)}`;
|
|
35
|
+
/** Unlink only these private downloads, not original data, journals or backups. */
|
|
36
|
+
export async function discardPrivateDataInputs(job) {
|
|
37
|
+
const parent = join(await privateDirectory(), 'execution-inputs');
|
|
38
|
+
const parentRow = await lstat(parent).catch(error => { if (error.code === 'ENOENT')
|
|
39
|
+
return null; throw error; });
|
|
40
|
+
if (!parentRow)
|
|
41
|
+
return;
|
|
42
|
+
if (!parentRow.isDirectory() || parentRow.isSymbolicLink() || parentRow.mode & 0o077 || process.getuid && parentRow.uid !== process.getuid())
|
|
43
|
+
throw invalid();
|
|
44
|
+
const directory = join(parent, [job.owner, job.workspace, job.job, job.lease].map(identifier).join('-'));
|
|
45
|
+
const folder = await lstat(directory).catch(error => { if (error.code === 'ENOENT')
|
|
46
|
+
return null; throw error; });
|
|
47
|
+
if (!folder)
|
|
48
|
+
return;
|
|
49
|
+
if (!folder.isDirectory() || folder.isSymbolicLink() || folder.mode & 0o077 || process.getuid && folder.uid !== process.getuid())
|
|
50
|
+
throw invalid();
|
|
51
|
+
for (const name of ['world-dataset.json', 'world-dataset.json.partial', 'initial-checkpoint.json', 'initial-checkpoint.json.partial', 'initial-application-checkpoint.json', 'initial-application-checkpoint.json.partial']) {
|
|
52
|
+
const path = join(directory, name);
|
|
53
|
+
const before = await lstat(path).catch(error => { if (error.code === 'ENOENT')
|
|
54
|
+
return null; throw error; });
|
|
55
|
+
if (!before)
|
|
56
|
+
continue;
|
|
57
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.mode & 0o077
|
|
58
|
+
|| before.size > (name.startsWith('initial-application') ? MAXIMUM.application_checkpoint : name.startsWith('initial-checkpoint') ? MAXIMUM.checkpoint : MAXIMUM.dataset)
|
|
59
|
+
|| process.getuid && before.uid !== process.getuid())
|
|
60
|
+
throw invalid();
|
|
61
|
+
await unlink(path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function privateFolder(path) {
|
|
65
|
+
await mkdir(path, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST')
|
|
66
|
+
throw error; });
|
|
67
|
+
const row = await lstat(path);
|
|
68
|
+
if (!row.isDirectory() || row.isSymbolicLink() || row.mode & 0o077 || process.getuid && row.uid !== process.getuid()) {
|
|
69
|
+
throw new CliError('local_input_storage_unsafe', 'Execution input storage must be private and owned by this OS user.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function privateFile(path, writable = false) {
|
|
73
|
+
const before = await lstat(path);
|
|
74
|
+
if (!before.isFile() || before.isSymbolicLink() || before.mode & 0o077 || before.nlink !== 1
|
|
75
|
+
|| process.getuid && before.uid !== process.getuid())
|
|
76
|
+
throw invalid();
|
|
77
|
+
const file = await open(path, (writable ? constants.O_RDWR : constants.O_RDONLY) | (constants.O_NOFOLLOW ?? 0));
|
|
78
|
+
const actual = await file.stat();
|
|
79
|
+
if (actual.dev !== before.dev || actual.ino !== before.ino || actual.size !== before.size) {
|
|
80
|
+
await file.close();
|
|
81
|
+
throw invalid();
|
|
82
|
+
}
|
|
83
|
+
return file;
|
|
84
|
+
}
|
|
85
|
+
function checkManifest(result, job, expected) {
|
|
86
|
+
const value = result.manifest;
|
|
87
|
+
if (!localInputPacketMatches(result, job) || !value || value.schema !== 'firedrill.local-input-manifest.v1'
|
|
88
|
+
|| !HASH.test(result.manifest_sha256) || sha(canonical(value)) !== result.manifest_sha256
|
|
89
|
+
|| expected && result.manifest_sha256 !== expected || value.job_id !== job.job || value.grant_id !== job.grant
|
|
90
|
+
|| value.payload_digest !== job.payloadDigest || value.purpose !== job.purpose
|
|
91
|
+
|| canonical(value.runtime) !== canonical(job.runtime) || !Array.isArray(value.entries)
|
|
92
|
+
|| value.entries.length !== (job.purpose === 'world_session' ? 3 : 4)
|
|
93
|
+
+ (['world_session', 'trial'].includes(job.purpose) && value.entries.some(row => row?.part === 'dataset') ? 1 : 0)
|
|
94
|
+
+ (['world_session', 'trial'].includes(job.purpose) && value.entries.some(row => row?.part === 'checkpoint') ? 1 : 0)
|
|
95
|
+
+ (['world_session', 'trial'].includes(job.purpose) && value.entries.some(row => row?.part === 'application_checkpoint') ? 1 : 0))
|
|
96
|
+
throw invalid();
|
|
97
|
+
if (typeof value.evidence_recovery_until !== 'string' || !Number.isSafeInteger(Date.parse(value.evidence_recovery_until))
|
|
98
|
+
|| Date.parse(value.evidence_recovery_until) <= Date.now())
|
|
99
|
+
throw new CliError('local_evidence_retention_missing', 'The original inputs lack a current finite evidence policy. No local target was started. Inspect original recovery metadata or explicitly forget retained evidence after cleanup.');
|
|
100
|
+
const names = new Set();
|
|
101
|
+
const requiredParts = new Set(job.purpose === 'world_session' ? ['source', 'compiled', 'packages']
|
|
102
|
+
: ['source', job.purpose === 'trial' ? 'compiled' : 'candidate', 'packages', 'scenarios']);
|
|
103
|
+
if (['trial', 'world_session'].includes(job.purpose) && value.entries.some(row => row?.part === 'dataset'))
|
|
104
|
+
requiredParts.add('dataset');
|
|
105
|
+
if (['world_session', 'trial'].includes(job.purpose) && value.entries.some(row => row?.part === 'checkpoint')) {
|
|
106
|
+
if (requiredParts.has('dataset') || job.purpose === 'trial' && !value.entries.some(row => row?.part === 'application_checkpoint'))
|
|
107
|
+
throw invalid();
|
|
108
|
+
requiredParts.add('checkpoint');
|
|
109
|
+
}
|
|
110
|
+
if (['world_session', 'trial'].includes(job.purpose) && value.entries.some(row => row?.part === 'application_checkpoint')) {
|
|
111
|
+
if (job.purpose === 'trial' && requiredParts.has('dataset'))
|
|
112
|
+
throw invalid();
|
|
113
|
+
requiredParts.add('application_checkpoint');
|
|
114
|
+
}
|
|
115
|
+
for (const entry of value.entries) {
|
|
116
|
+
if (!entry || !requiredParts.has(entry.part) || !Object.hasOwn(FILES, entry.part) || names.has(entry.part) || !HASH.test(entry.sha256)
|
|
117
|
+
|| !Number.isSafeInteger(entry.size) || entry.size <= 0 || entry.size > MAXIMUM[entry.part])
|
|
118
|
+
throw invalid();
|
|
119
|
+
names.add(entry.part);
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
async function readOnce(job, signal, part, offset = 0, length = 0, manifestSha256 = null) {
|
|
124
|
+
const auth = { actor: job.owner, workspace: job.workspace, signal };
|
|
125
|
+
const receipt = await readLocalClaim(job, job.device, job.job, job.request, signal);
|
|
126
|
+
if (receipt.job_id !== job.job || receipt.grant_id !== job.grant || receipt.device_key !== job.device
|
|
127
|
+
|| receipt.payload_digest !== job.payloadDigest || receipt.purpose !== job.purpose || receipt.claim?.id !== job.lease
|
|
128
|
+
|| receipt.claim.attempt_id !== job.attempt || receipt.claim.request_key !== job.request || receipt.claim.state !== 'claimed')
|
|
129
|
+
throw invalid();
|
|
130
|
+
const sentAt = Date.parse(receipt.server_time), expires = Date.parse(receipt.claim.expires_at);
|
|
131
|
+
if (!Number.isSafeInteger(sentAt) || !Number.isFinite(expires) || expires <= sentAt)
|
|
132
|
+
throw invalid();
|
|
133
|
+
const payload = { schema: 'firedrill.local-input-read.v1',
|
|
134
|
+
job_id: job.job, device_key: job.device, request_key: job.request, native_session_id: job.nativeSession,
|
|
135
|
+
lease_id: job.lease, part, offset, length, manifest_sha256: manifestSha256, read_key: randomUUID(), sent_at_ms: sentAt };
|
|
136
|
+
const signed = await signRunnerRequest(job, job.device, payload, signal);
|
|
137
|
+
// A read may be repeated after reconciliation, but it never renews a lease,
|
|
138
|
+
// allocates another attempt or replays a launch. No control token enters files.
|
|
139
|
+
const value = await api(prefix(job) + '/inputs', { ...auth, method: 'POST', body: signed });
|
|
140
|
+
if (!value || (value.schema === 'firedrill.local-input-pending.v1' ? part !== 'manifest' || !localInputPendingMatches(value, job)
|
|
141
|
+
: !localInputPacketMatches(value, job, { part, offset, length })))
|
|
142
|
+
throw invalid();
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
async function read(job, signal, part, offset = 0, length = 0, manifestSha256 = null) {
|
|
146
|
+
while (true) {
|
|
147
|
+
const result = await readOnce(job, signal, part, offset, length, manifestSha256);
|
|
148
|
+
if (result.schema !== 'firedrill.local-input-pending.v1')
|
|
149
|
+
return result;
|
|
150
|
+
if (part !== 'manifest' || result.job_id !== job.job || result.lease_id !== job.lease
|
|
151
|
+
|| !['preparing_packages', 'preparing_starting_data'].includes(result.input_state) || !Number.isSafeInteger(result.retry_after_ms)
|
|
152
|
+
|| result.retry_after_ms < 250 || result.retry_after_ms > 10000)
|
|
153
|
+
throw invalid();
|
|
154
|
+
// Wait only on the actual retained asynchronous input state. The existing
|
|
155
|
+
// keeper owns lease renewal; this read loop cannot claim/restart a job.
|
|
156
|
+
await sleep(result.retry_after_ms, undefined, { signal });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function hashFile(path, maximum) {
|
|
160
|
+
const file = await privateFile(path);
|
|
161
|
+
try {
|
|
162
|
+
const size = (await file.stat()).size;
|
|
163
|
+
if (size > maximum)
|
|
164
|
+
throw invalid();
|
|
165
|
+
const digest = createHash('sha256'), buffer = Buffer.allocUnsafe(CHUNK);
|
|
166
|
+
let position = 0;
|
|
167
|
+
while (position < size) {
|
|
168
|
+
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, size - position), position);
|
|
169
|
+
if (!bytesRead)
|
|
170
|
+
throw invalid();
|
|
171
|
+
digest.update(buffer.subarray(0, bytesRead));
|
|
172
|
+
position += bytesRead;
|
|
173
|
+
}
|
|
174
|
+
if ((await file.stat()).size !== size)
|
|
175
|
+
throw invalid();
|
|
176
|
+
return { size, sha256: digest.digest('hex') };
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
await file.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function retainManifest(directory, result) {
|
|
183
|
+
const path = join(directory, 'manifest.json');
|
|
184
|
+
const body = JSON.stringify({ schema: 'firedrill.local-retained-inputs.v1', manifest: result.manifest, sha256: result.manifest_sha256 });
|
|
185
|
+
try {
|
|
186
|
+
const file = await open(path, 'wx', 0o600);
|
|
187
|
+
try {
|
|
188
|
+
await file.writeFile(body);
|
|
189
|
+
await file.sync();
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await file.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (error.code !== 'EEXIST')
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
const file = await privateFile(path);
|
|
200
|
+
try {
|
|
201
|
+
if ((await file.stat()).size > 65536 || await file.readFile('utf8') !== body)
|
|
202
|
+
throw invalid();
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
await file.close();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Internal executor input transfer. Not a standalone run or launch command. */
|
|
209
|
+
export async function receiveLocalInputs(job, signal) {
|
|
210
|
+
for (const value of [job.owner, job.workspace, job.device, job.job, job.request, job.lease, job.attempt, job.nativeSession, job.grant])
|
|
211
|
+
identifier(value);
|
|
212
|
+
if (!HASH.test(job.payloadDigest) || !['validation', 'trial', 'world_session'].includes(job.purpose) || !job.runtime
|
|
213
|
+
|| job.runtime.os !== 'linux' || !['amd64', 'arm64'].includes(job.runtime.arch) || !IMAGE.test(job.runtime.image)
|
|
214
|
+
|| !/^[a-f0-9]{40}$/.test(job.runtime.source_revision) || job.runtime.profile !== 'firedrill.local-container.v1')
|
|
215
|
+
throw invalid();
|
|
216
|
+
const parent = join(await privateDirectory(), 'execution-inputs');
|
|
217
|
+
await privateFolder(parent);
|
|
218
|
+
const directory = join(parent, [job.owner, job.workspace, job.job, job.lease].join('-'));
|
|
219
|
+
await privateFolder(directory);
|
|
220
|
+
const lock = await processLifetimeLock(join(directory, 'transfer.lock'));
|
|
221
|
+
signal = AbortSignal.any([signal, lock.signal]);
|
|
222
|
+
let completed = false;
|
|
223
|
+
try {
|
|
224
|
+
lock.assertHeld();
|
|
225
|
+
signal.throwIfAborted();
|
|
226
|
+
const result = await read(job, signal, 'manifest');
|
|
227
|
+
const manifest = checkManifest(result, job);
|
|
228
|
+
if (result.part !== 'manifest' || result.offset !== 0)
|
|
229
|
+
throw invalid();
|
|
230
|
+
await retainManifest(directory, result);
|
|
231
|
+
const files = {};
|
|
232
|
+
for (const entry of manifest.entries) {
|
|
233
|
+
const path = join(directory, FILES[entry.part]), partial = path + '.partial';
|
|
234
|
+
const final = await lstat(path).catch(error => { if (error.code === 'ENOENT')
|
|
235
|
+
return null; throw error; });
|
|
236
|
+
if (final) {
|
|
237
|
+
const actual = await hashFile(path, entry.size);
|
|
238
|
+
if (actual.size !== entry.size || actual.sha256 !== entry.sha256)
|
|
239
|
+
throw invalid();
|
|
240
|
+
files[entry.part] = path;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const created = await open(partial, 'wx', 0o600);
|
|
245
|
+
await created.close();
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (error.code !== 'EEXIST')
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
const file = await privateFile(partial, true);
|
|
252
|
+
try {
|
|
253
|
+
let position = (await file.stat()).size;
|
|
254
|
+
if (position > entry.size)
|
|
255
|
+
throw invalid();
|
|
256
|
+
while (position < entry.size) {
|
|
257
|
+
signal.throwIfAborted();
|
|
258
|
+
const length = Math.min(CHUNK, entry.size - position);
|
|
259
|
+
const chunk = await read(job, signal, entry.part, position, length, result.manifest_sha256);
|
|
260
|
+
checkManifest(chunk, job, result.manifest_sha256);
|
|
261
|
+
if (chunk.part === 'manifest' || chunk.part !== entry.part || chunk.offset !== position || chunk.length !== length || typeof chunk.content_base64 !== 'string'
|
|
262
|
+
|| chunk.content_base64.length !== 4 * Math.ceil(length / 3) || !HASH.test(chunk.chunk_sha256 ?? ''))
|
|
263
|
+
throw invalid();
|
|
264
|
+
const bytes = Buffer.from(chunk.content_base64, 'base64');
|
|
265
|
+
if (bytes.length !== length || bytes.toString('base64') !== chunk.content_base64 || sha(bytes) !== chunk.chunk_sha256)
|
|
266
|
+
throw invalid();
|
|
267
|
+
let written = 0;
|
|
268
|
+
while (written < bytes.length) {
|
|
269
|
+
const next = await file.write(bytes, written, bytes.length - written, position + written);
|
|
270
|
+
if (!next.bytesWritten)
|
|
271
|
+
throw invalid();
|
|
272
|
+
written += next.bytesWritten;
|
|
273
|
+
}
|
|
274
|
+
await file.sync();
|
|
275
|
+
position += length;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
await file.close();
|
|
280
|
+
}
|
|
281
|
+
const actual = await hashFile(partial, entry.size);
|
|
282
|
+
if (actual.size !== entry.size || actual.sha256 !== entry.sha256)
|
|
283
|
+
throw invalid();
|
|
284
|
+
// Only fixed logical filenames are used. No extraction or project writes.
|
|
285
|
+
if (await lstat(path).then(() => true).catch(error => { if (error.code === 'ENOENT')
|
|
286
|
+
return false; throw error; }))
|
|
287
|
+
throw invalid();
|
|
288
|
+
await rename(partial, path);
|
|
289
|
+
files[entry.part] = path;
|
|
290
|
+
}
|
|
291
|
+
// Reject changed/withdrawn ownership after receiving the final bytes too.
|
|
292
|
+
const final = await read(job, signal, 'manifest');
|
|
293
|
+
checkManifest(final, job, result.manifest_sha256);
|
|
294
|
+
lock.assertHeld();
|
|
295
|
+
signal.throwIfAborted();
|
|
296
|
+
completed = true;
|
|
297
|
+
return { directory, manifest, manifestSha256: result.manifest_sha256, files };
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
try {
|
|
301
|
+
if (!completed)
|
|
302
|
+
await discardPrivateDataInputs(job);
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
await lock.close();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { localAssignmentsPage, readLocalClaim, changeLocalClaim } from './runner-claim-reads.js';
|
|
2
|
+
import { CliError } from './http.js';
|
|
3
|
+
import { selectedWorkspace } from './world-access.js';
|
|
4
|
+
import { identifier } from './world-contracts.js';
|
|
5
|
+
function describe(row) {
|
|
6
|
+
return `${row.job_id} ${row.world_name} ${row.purpose} ${row.job_state}\n`
|
|
7
|
+
+ (row.claim ? `Claim ${row.claim.request_key}: ${row.claim.state}. Cleanup ${row.claim.cleanup_recorded ? 'recorded' : 'not recorded'}.`
|
|
8
|
+
: 'No retained claim. A delayed ownership request may still arrive.')
|
|
9
|
+
+ (row.input_preparation && ['pending', 'preparing', 'ready', 'failed'].includes(row.input_preparation.state)
|
|
10
|
+
? `\nPackage inputs: ${row.input_preparation.state}.` + (row.input_preparation.state === 'failed' ? ' Review the source dependency locks. No target outcome is implied.' : '') : '');
|
|
11
|
+
}
|
|
12
|
+
export async function runnerJobs(action, device, job, request, options, output) {
|
|
13
|
+
device = identifier(device);
|
|
14
|
+
const scope = await selectedWorkspace({ workspace: options.workspace,
|
|
15
|
+
interactive: !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY,
|
|
16
|
+
openBrowser: options.openBrowser, signal: options.signal });
|
|
17
|
+
if (action === 'list') {
|
|
18
|
+
const page = options.page ?? '1';
|
|
19
|
+
if (!/^[1-9][0-9]*$/.test(page) || !Number.isSafeInteger(Number(page)))
|
|
20
|
+
throw new CliError('page_invalid', 'Use a positive page number.', 2);
|
|
21
|
+
const result = await localAssignmentsPage(scope, device, { page: Number(page), query: options.search }, options.signal);
|
|
22
|
+
output('local_job_directory', result, result.results.map(describe).join('\n\n') || 'No saved jobs assigned to this runner.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!job || !request)
|
|
26
|
+
throw new CliError('local_claim_identity_required', 'Use the exact device, job and claim request IDs.', 2);
|
|
27
|
+
job = identifier(job);
|
|
28
|
+
request = identifier(request);
|
|
29
|
+
let receipt;
|
|
30
|
+
if (action === 'withdraw') {
|
|
31
|
+
try {
|
|
32
|
+
receipt = await changeLocalClaim(scope, device, job, request, 'withdraw', undefined, options.signal);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Reconcile the exact withdrawal after a lost reply. No new claim, job,
|
|
36
|
+
// attempt or resource is created by recovery.
|
|
37
|
+
receipt = await readLocalClaim(scope, device, job, request, options.signal);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else
|
|
41
|
+
receipt = await readLocalClaim(scope, device, job, request, options.signal);
|
|
42
|
+
if (receipt.job_id !== job || receipt.device_key !== device || receipt.claim && receipt.claim.request_key !== request
|
|
43
|
+
|| !receipt.claim && receipt.request_key !== request)
|
|
44
|
+
throw new CliError('local_claim_receipt_invalid', 'The saved ownership receipt does not match this request.');
|
|
45
|
+
output('local_job_claim', receipt, describe(receipt));
|
|
46
|
+
if (action === 'withdraw' && receipt.claim?.state !== 'withdrawn') {
|
|
47
|
+
throw new CliError('local_claim_withdrawal_uncertain', 'Withdrawal is not confirmed. Inspect this exact claim before continuing.');
|
|
48
|
+
}
|
|
49
|
+
}
|