firedrill-run 2.2.5 → 3.0.1
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 +176 -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 +506 -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 +1627 -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 +96 -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,268 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { api } from './auth.js';
|
|
4
|
+
import { CliError } from './http.js';
|
|
5
|
+
import { identifier } from './world-contracts.js';
|
|
6
|
+
import { signRunnerRequest } from './runner-identity.js';
|
|
7
|
+
import { localWorldClaimPath, localWorldClaimTime } from './runner-world-claim.js';
|
|
8
|
+
import { retainLocalWorldObservation } from './runner-world-observations.js';
|
|
9
|
+
import { LocalWorldCommandEvidence } from './runner-world-command-evidence.js';
|
|
10
|
+
import { localWorldIdentityMatches, LOCAL_WORLD_IDENTITY_KEYS } from '../../contracts/local-world-native-contracts.js';
|
|
11
|
+
const HASH = /^[a-f0-9]{64}$/, CHUNK = 65536, MAX_BODY = 8 * 1024 ** 2;
|
|
12
|
+
const KINDS = ['http', 'mcp', 'stop', 'surface_open', 'surface_action', 'state_capture', 'world_reset', 'checkpoint_save', 'checkpoint_load',
|
|
13
|
+
'voice_start', 'voice_audio', 'voice_listen', 'voice_stop', 'app_save', 'paired_save'];
|
|
14
|
+
const FINISHED = ['observed', 'not_dispatched', 'unknown', 'stop_requested'];
|
|
15
|
+
const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
const exact = (value, fields) => Object.keys(value).sort().join(',') === [...fields].sort().join(',');
|
|
17
|
+
const sha = (body) => createHash('sha256').update(body).digest('hex');
|
|
18
|
+
const invalid = () => new CliError('local_world_command_changed', 'The saved command does not match this exact local world. No action was repeated.');
|
|
19
|
+
function command(value) {
|
|
20
|
+
if (value === null)
|
|
21
|
+
return;
|
|
22
|
+
if (!record(value) || !exact(value, ['id', 'sequence', 'kind', 'state', 'sha256', 'not_after', 'publication_key', 'publication_started_at'])
|
|
23
|
+
|| typeof value.id !== 'string' || identifier(value.id) !== value.id
|
|
24
|
+
|| typeof value.sequence !== 'string' || !/^[1-9][0-9]{0,15}$/.test(value.sequence)
|
|
25
|
+
|| !Number.isSafeInteger(Number(value.sequence)) || typeof value.kind !== 'string' || !KINDS.includes(value.kind)
|
|
26
|
+
|| typeof value.state !== 'string' || !['queued', 'publishing', 'accepted', ...FINISHED].includes(value.state)
|
|
27
|
+
|| typeof value.sha256 !== 'string' || !HASH.test(value.sha256) || typeof value.not_after !== 'string'
|
|
28
|
+
|| !Number.isFinite(Date.parse(value.not_after))
|
|
29
|
+
|| (value.publication_key === null) !== (value.publication_started_at === null)
|
|
30
|
+
|| value.publication_key !== null && (typeof value.publication_key !== 'string' || identifier(value.publication_key) !== value.publication_key
|
|
31
|
+
|| typeof value.publication_started_at !== 'string' || !Number.isFinite(Date.parse(value.publication_started_at))))
|
|
32
|
+
throw invalid();
|
|
33
|
+
}
|
|
34
|
+
function same(left, right) {
|
|
35
|
+
return ['id', 'sequence', 'kind', 'sha256', 'not_after'].every(key => left[key] === right[key]);
|
|
36
|
+
}
|
|
37
|
+
function bytes(value, size, maximum, digest) {
|
|
38
|
+
if (typeof value !== 'string' || !Number.isSafeInteger(size) || size < 0 || size > maximum
|
|
39
|
+
|| value.length !== 4 * Math.ceil(size / 3) || typeof digest !== 'string' || !HASH.test(digest))
|
|
40
|
+
throw invalid();
|
|
41
|
+
const body = Buffer.from(value, 'base64');
|
|
42
|
+
if (body.length !== size || body.toString('base64') !== value || sha(body) !== digest) {
|
|
43
|
+
body.fill(0);
|
|
44
|
+
throw invalid();
|
|
45
|
+
}
|
|
46
|
+
return body;
|
|
47
|
+
}
|
|
48
|
+
/** Private outbound command transport, not local-session admission. The cloud
|
|
49
|
+
* publication intent is committed before the sole native write. An existing or
|
|
50
|
+
* uncertain publication is read back by ORIGINAL id/hash, never sent again.
|
|
51
|
+
* Evidence collection, observation retention and lifecycle settlement are owned
|
|
52
|
+
* by the enclosing collector, not fabricated by returning from this transport. */
|
|
53
|
+
export class LocalWorldCommandDelivery {
|
|
54
|
+
busy = false;
|
|
55
|
+
retainedObservation = null;
|
|
56
|
+
retainedObservationShape = null;
|
|
57
|
+
lastObservationRetention = -Infinity;
|
|
58
|
+
evidence = null;
|
|
59
|
+
owner;
|
|
60
|
+
configuration;
|
|
61
|
+
channel;
|
|
62
|
+
signal;
|
|
63
|
+
constructor(owner, configuration, channel, signal) {
|
|
64
|
+
this.owner = owner;
|
|
65
|
+
this.configuration = configuration;
|
|
66
|
+
this.channel = channel;
|
|
67
|
+
this.signal = signal;
|
|
68
|
+
if (configuration.control.schema !== 'firedrill.world-session.v1' || !owner.evidenceUpload
|
|
69
|
+
|| !HASH.test(configuration.manifest_sha256))
|
|
70
|
+
throw invalid();
|
|
71
|
+
}
|
|
72
|
+
evidenceStore() {
|
|
73
|
+
return this.evidence ??= LocalWorldCommandEvidence.open(this.owner, this.configuration, this.channel, this.signal);
|
|
74
|
+
}
|
|
75
|
+
async close() {
|
|
76
|
+
if (this.busy)
|
|
77
|
+
throw invalid();
|
|
78
|
+
if (this.evidence)
|
|
79
|
+
await (await this.evidence).close();
|
|
80
|
+
}
|
|
81
|
+
async request(input) {
|
|
82
|
+
const owner = this.owner, sent = await localWorldClaimTime(owner, this.signal);
|
|
83
|
+
const action = input.action, selected = input.action === 'next' ? undefined : input.selected;
|
|
84
|
+
const selection = selected ? { command_id: selected.id, command_sha256: selected.sha256 } : null;
|
|
85
|
+
const fields = input.action === 'next' ? { action: 'next' }
|
|
86
|
+
: input.action === 'packet' ? { action: 'packet', command_id: selection.command_id, command_sha256: selection.command_sha256 }
|
|
87
|
+
: input.action === 'publish' ? { action: 'publish', command_id: selection.command_id, command_sha256: selection.command_sha256, publication_key: input.publication_key }
|
|
88
|
+
: input.action === 'body' ? { action: 'body', command_id: selection.command_id, command_sha256: selection.command_sha256, offset: input.offset, length: input.length }
|
|
89
|
+
: { action: 'checkpoint', command_id: selection.command_id, command_sha256: selection.command_sha256, offset: input.offset, length: input.length };
|
|
90
|
+
const payload = { schema: 'firedrill.local-world-command-read.v1',
|
|
91
|
+
job_id: owner.job, attempt_id: owner.attempt, lease_id: owner.lease, request_key: owner.request,
|
|
92
|
+
device_key: owner.device, native_session_id: owner.nativeSession, manifest_sha256: this.configuration.manifest_sha256,
|
|
93
|
+
session_id: owner.session.session_id, input_sha256: owner.session.input_sha256, read_key: randomUUID(), sent_at_ms: sent, ...fields };
|
|
94
|
+
const proof = await signRunnerRequest(owner, owner.device, payload, this.signal);
|
|
95
|
+
// Neither this API client nor the private native channel retries a write.
|
|
96
|
+
const result = await api(localWorldClaimPath(owner) + '/world-commands', { actor: owner.owner,
|
|
97
|
+
workspace: owner.workspace, signal: this.signal, method: 'POST', body: proof });
|
|
98
|
+
const extra = action === 'packet' ? ['packet_base64', 'body'] : ['body', 'checkpoint'].includes(action) ? ['offset', 'length', 'sha256', 'content_base64'] : [];
|
|
99
|
+
if (!record(result) || !localWorldIdentityMatches(result, owner, this.configuration.manifest_sha256)
|
|
100
|
+
|| !exact(result, [...LOCAL_WORLD_IDENTITY_KEYS.filter(key => key !== 'manifest_sha256'), 'schema', 'session_id', 'lease_id', 'manifest_sha256', 'input_sha256',
|
|
101
|
+
'action', 'command', 'server_time', 'provenance', ...extra])
|
|
102
|
+
|| result.schema !== 'firedrill.local-world-command-delivery.v1' || result.session_id !== owner.session.session_id
|
|
103
|
+
|| result.lease_id !== owner.lease || result.manifest_sha256 !== this.configuration.manifest_sha256
|
|
104
|
+
|| result.input_sha256 !== owner.session.input_sha256 || result.action !== action || result.provenance !== 'customer_device'
|
|
105
|
+
|| typeof result.server_time !== 'string' || !Number.isFinite(Date.parse(result.server_time)))
|
|
106
|
+
throw invalid();
|
|
107
|
+
command(result.command);
|
|
108
|
+
if (selected && (!result.command || !same(selected, result.command)))
|
|
109
|
+
throw invalid();
|
|
110
|
+
if (result.command && Date.parse(result.command.not_after) > Date.parse(owner.session.expires_at))
|
|
111
|
+
throw invalid();
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
async next() {
|
|
115
|
+
if (this.busy)
|
|
116
|
+
throw invalid();
|
|
117
|
+
this.busy = true;
|
|
118
|
+
try {
|
|
119
|
+
await this.readObservation();
|
|
120
|
+
const result = await this.request({ action: 'next' });
|
|
121
|
+
if (result.command && FINISHED.includes(result.command.state))
|
|
122
|
+
throw invalid();
|
|
123
|
+
return result.command;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
this.busy = false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async observe() {
|
|
130
|
+
if (this.busy)
|
|
131
|
+
throw invalid();
|
|
132
|
+
this.busy = true;
|
|
133
|
+
try {
|
|
134
|
+
return await this.readObservation();
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
this.busy = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async readObservation() {
|
|
141
|
+
const observation = await this.channel.observe(), body = Buffer.from(JSON.stringify(observation));
|
|
142
|
+
// This is upload cadence only, not a simulation clock, invented delay or
|
|
143
|
+
// altered evidence. Keep changes promptly; coalesce unchanged heartbeats.
|
|
144
|
+
const comparable = Buffer.from(JSON.stringify({ ...observation, status: observation.status === null ? null
|
|
145
|
+
: Object.fromEntries(Object.entries(observation.status).filter(([key]) => !['observed_at', 'elapsed_ms'].includes(key))) }));
|
|
146
|
+
let digest, shape;
|
|
147
|
+
try {
|
|
148
|
+
digest = sha(body);
|
|
149
|
+
shape = sha(comparable);
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
body.fill(0);
|
|
153
|
+
comparable.fill(0);
|
|
154
|
+
}
|
|
155
|
+
if (this.retainedObservation !== digest && (this.retainedObservationShape !== shape || performance.now() - this.lastObservationRetention >= 15000)) {
|
|
156
|
+
await retainLocalWorldObservation(this.owner, this.configuration, observation, this.signal);
|
|
157
|
+
this.retainedObservation = digest;
|
|
158
|
+
this.retainedObservationShape = shape;
|
|
159
|
+
this.lastObservationRetention = performance.now();
|
|
160
|
+
}
|
|
161
|
+
return observation;
|
|
162
|
+
}
|
|
163
|
+
inspectNative(value, selected) {
|
|
164
|
+
if (value.session_id !== this.owner.session.session_id || value.operation_id !== this.owner.lease
|
|
165
|
+
|| value.command_id !== selected.id || value.command_sha256 !== selected.sha256 || value.sequence !== Number(selected.sequence))
|
|
166
|
+
throw invalid();
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
/** Recovery is observation only. A missing native receipt stays unknown. */
|
|
170
|
+
async recover(selected) {
|
|
171
|
+
command(selected);
|
|
172
|
+
if (this.busy)
|
|
173
|
+
throw invalid();
|
|
174
|
+
this.busy = true;
|
|
175
|
+
try {
|
|
176
|
+
return await this.readResult(selected);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
this.busy = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async readResult(selected) {
|
|
183
|
+
const evidence = await this.evidenceStore();
|
|
184
|
+
const saved = await evidence.saved(selected);
|
|
185
|
+
const value = this.inspectNative(saved ?? await this.channel.result(selected.id), selected);
|
|
186
|
+
return evidence.retain(selected, value);
|
|
187
|
+
}
|
|
188
|
+
async deliver(selected) {
|
|
189
|
+
command(selected);
|
|
190
|
+
if (this.busy || FINISHED.includes(selected.state))
|
|
191
|
+
throw invalid();
|
|
192
|
+
this.busy = true;
|
|
193
|
+
let packet = null, body = null;
|
|
194
|
+
try {
|
|
195
|
+
const evidence = await this.evidenceStore();
|
|
196
|
+
await evidence.remember(selected); // Durable encrypted identity before publication authority.
|
|
197
|
+
const saved = await this.request({ action: 'packet', selected }), current = saved.command;
|
|
198
|
+
if (current.publication_key !== null)
|
|
199
|
+
return await this.readResult(current);
|
|
200
|
+
if (current.state !== 'queued' || !record(saved.body) || !exact(saved.body, ['size', 'sha256'])
|
|
201
|
+
|| !Number.isSafeInteger(saved.body.size) || Number(saved.body.size) < 0 || Number(saved.body.size) > MAX_BODY
|
|
202
|
+
|| typeof saved.body.sha256 !== 'string' || !HASH.test(saved.body.sha256) || typeof saved.packet_base64 !== 'string')
|
|
203
|
+
throw invalid();
|
|
204
|
+
// Base64 length alone cannot determine the decoded size; bounded decoding
|
|
205
|
+
// is checked against the original retained command hash before use.
|
|
206
|
+
if (saved.packet_base64.length > 4 * Math.ceil(65536 / 3))
|
|
207
|
+
throw invalid();
|
|
208
|
+
packet = Buffer.from(saved.packet_base64, 'base64');
|
|
209
|
+
if (!packet.length || packet.length > 65536 || packet.toString('base64') !== saved.packet_base64 || sha(packet) !== selected.sha256)
|
|
210
|
+
throw invalid();
|
|
211
|
+
let document;
|
|
212
|
+
try {
|
|
213
|
+
document = JSON.parse(packet.toString('utf8'));
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
throw invalid();
|
|
217
|
+
}
|
|
218
|
+
if (!record(document) || !exact(document, ['schema', 'session_id', 'operation_id', 'command_id', 'sequence', 'kind', 'not_after', 'request'])
|
|
219
|
+
|| document.schema !== 'firedrill.world-session-command.v1' || document.session_id !== this.owner.session.session_id
|
|
220
|
+
|| document.operation_id !== this.owner.lease || document.command_id !== selected.id || document.sequence !== Number(selected.sequence)
|
|
221
|
+
|| document.kind !== selected.kind || document.not_after !== selected.not_after || !record(document.request))
|
|
222
|
+
throw invalid();
|
|
223
|
+
body = Buffer.alloc(Number(saved.body.size));
|
|
224
|
+
for (let offset = 0; offset < body.length; offset += CHUNK) {
|
|
225
|
+
const length = Math.min(CHUNK, body.length - offset), part = await this.request({ action: 'body', selected, offset, length });
|
|
226
|
+
if (part.command.publication_key !== null)
|
|
227
|
+
return await this.readResult(part.command);
|
|
228
|
+
if (part.offset !== offset || part.length !== length)
|
|
229
|
+
throw invalid();
|
|
230
|
+
const chunk = bytes(part.content_base64, length, CHUNK, part.sha256);
|
|
231
|
+
try {
|
|
232
|
+
chunk.copy(body, offset);
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
chunk.fill(0);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (sha(body) !== saved.body.sha256 || ['http', 'mcp'].includes(selected.kind)
|
|
239
|
+
&& (document.request.body_size !== body.length || document.request.body_sha256 !== saved.body.sha256))
|
|
240
|
+
throw invalid();
|
|
241
|
+
if (selected.kind === 'checkpoint_load') {
|
|
242
|
+
if (body.length)
|
|
243
|
+
throw invalid();
|
|
244
|
+
await this.channel.checkpointInput(packet, async (offset, length) => {
|
|
245
|
+
const part = await this.request({ action: 'checkpoint', selected, offset, length });
|
|
246
|
+
if (part.command.publication_key !== null || part.command.state !== 'queued'
|
|
247
|
+
|| part.offset !== offset || part.length !== length)
|
|
248
|
+
throw invalid();
|
|
249
|
+
return bytes(part.content_base64, length, CHUNK, part.sha256);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const publication = randomUUID(), admitted = await this.request({ action: 'publish', selected, publication_key: publication });
|
|
253
|
+
if (admitted.command?.publication_key !== publication || admitted.command.state !== 'publishing'
|
|
254
|
+
|| !admitted.command.publication_started_at)
|
|
255
|
+
throw invalid();
|
|
256
|
+
// The cloud has already committed a durable intent. Loss from here can
|
|
257
|
+
// only trigger original result() readback, not another native publish().
|
|
258
|
+
this.signal.throwIfAborted();
|
|
259
|
+
const value = this.inspectNative(await this.channel.publish(packet, body), selected);
|
|
260
|
+
return await evidence.retain(selected, value);
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
packet?.fill(0);
|
|
264
|
+
body?.fill(0);
|
|
265
|
+
this.busy = false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CliError } from './http.js';
|
|
2
|
+
import { localContainerCompletion } from './runner-container.js';
|
|
3
|
+
import { syncClosingReceipt } from './runner-completion.js';
|
|
4
|
+
const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
const fail = () => new CliError('local_world_closing_changed', 'The original local world cleanup record could not be confirmed. No world or command was restarted.');
|
|
6
|
+
function status(value, session, operation) {
|
|
7
|
+
if (value === null)
|
|
8
|
+
return null;
|
|
9
|
+
if (!record(value) || value.schema !== 'firedrill.world-session-status.v7' || value.session_id !== session || value.operation_id !== operation
|
|
10
|
+
|| value.target_invocation_observed !== false || value.behavior_assessed !== false || typeof value.state !== 'string'
|
|
11
|
+
|| !['starting', 'active', 'resetting', 'checkpointing', 'stopping', 'stopped', 'failed'].includes(value.state)
|
|
12
|
+
|| typeof value.code !== 'string' || value.code !== '' && !/^[a-z_]{1,64}$/.test(value.code))
|
|
13
|
+
throw fail();
|
|
14
|
+
const failed = value.state === 'failed';
|
|
15
|
+
// Cleanup carries lifecycle facts only. The original native status may also
|
|
16
|
+
// contain an applied dataset, fork, topology or private-filter reference;
|
|
17
|
+
// those stay in the separately gated evidence journal. Do not require their
|
|
18
|
+
// absence or open that journal to report this exact container's cleanup.
|
|
19
|
+
if (!failed && (typeof value.observed_at !== 'string' || !Number.isFinite(Date.parse(value.observed_at))
|
|
20
|
+
|| ['elapsed_ms', 'completed_command_sequence', 'reset_count'].some(key => !Number.isSafeInteger(value[key]) || Number(value[key]) < 0)
|
|
21
|
+
|| typeof value.native_cleanup_finished !== 'boolean' || value.native_cleanup_finished !== (value.state === 'stopped')))
|
|
22
|
+
throw fail();
|
|
23
|
+
return { schema: 'firedrill.local-world-closing-observation.v1', session_id: session, operation_id: operation,
|
|
24
|
+
target_invocation_observed: false, behavior_assessed: false, state: value.state, code: value.code,
|
|
25
|
+
observed_at: failed ? null : value.observed_at, elapsed_ms: failed ? null : value.elapsed_ms,
|
|
26
|
+
completed_command_sequence: failed ? null : value.completed_command_sequence,
|
|
27
|
+
reset_count: failed ? null : value.reset_count, native_cleanup_finished: failed ? null : value.native_cleanup_finished };
|
|
28
|
+
}
|
|
29
|
+
/** Reads existing private journals only. No Docker/provider lookup, execution
|
|
30
|
+
* claim/pulse, target/model/Vault activity, native publication or replacement key.
|
|
31
|
+
* The current authenticated member signs cleanup facts with the original device.
|
|
32
|
+
*/
|
|
33
|
+
export async function syncLocalWorldCompletion(scope, operation, signal, options = {}) {
|
|
34
|
+
const saved = await localContainerCompletion(scope, operation, 'world_session');
|
|
35
|
+
const identity = saved.worldEvidenceOwner, session = saved.document.world_session;
|
|
36
|
+
if (!identity || !session || identity.session.session_id !== session.session_id)
|
|
37
|
+
throw fail();
|
|
38
|
+
// This reference was journaled from the actual channel's validated final
|
|
39
|
+
// bytes before native release. It is NOT proof the bytes were saved locally
|
|
40
|
+
// or retained in GCS. Reporting cleanup must not depend on decrypting files.
|
|
41
|
+
const reference = saved.worldFinalStatus;
|
|
42
|
+
const native = reference ? { size: reference.size, sha256: reference.sha256 } : null;
|
|
43
|
+
const document = { ...saved.document, status: status(saved.document.status, session.session_id, saved.lease),
|
|
44
|
+
evidence: { schema: 'firedrill.local-world-closing-evidence.v1', native_status: native } };
|
|
45
|
+
return syncClosingReceipt(scope, { ...saved, document }, signal, options);
|
|
46
|
+
}
|
|
47
|
+
/** Report cleanup before saved-file transfer, then wait for settlement only
|
|
48
|
+
* after retention actually completed. This avoids waiting for the evidence
|
|
49
|
+
* worker while the same command has not yet supplied its original bytes.
|
|
50
|
+
*/
|
|
51
|
+
export async function syncLocalWorldClosing(scope, operation, signal, options = {}) {
|
|
52
|
+
let receipt = await syncLocalWorldCompletion(scope, operation, signal);
|
|
53
|
+
if (!receipt.received || receipt.settled)
|
|
54
|
+
return receipt;
|
|
55
|
+
const { syncLocalWorldEvidence } = await import('./runner-world-evidence-sync.js');
|
|
56
|
+
try {
|
|
57
|
+
const evidence = await syncLocalWorldEvidence(scope, operation, signal, options);
|
|
58
|
+
try {
|
|
59
|
+
receipt = await syncLocalWorldCompletion(scope, operation, signal, { wait: ['synced', 'access_removed', 'closed'].includes(evidence.state) });
|
|
60
|
+
return { ...receipt, evidence };
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
return { ...receipt, evidence, settlement_observation: 'unconfirmed',
|
|
65
|
+
settlement_code: error instanceof CliError ? error.code : 'local_world_closing_unconfirmed' };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
signal.throwIfAborted();
|
|
70
|
+
// Cleanup acceptance is an already observed fact. Failure to open/decrypt
|
|
71
|
+
// an optional evidence journal must not erase that separate original receipt.
|
|
72
|
+
return { ...receipt, evidence: { schema: 'firedrill.local-world-evidence-sync.v2', state: 'unconfirmed',
|
|
73
|
+
operation_id: operation, cleanup_receipt_received: receipt.received, cloud_settlement_observed: receipt.settled,
|
|
74
|
+
command_scan_complete: false, code: error instanceof CliError ? error.code : 'local_world_evidence_unconfirmed' } };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { CliError } from './http.js';
|
|
2
|
+
import { identifier } from './world-contracts.js';
|
|
3
|
+
import { selectOne } from './prompts.js';
|
|
4
|
+
import { localContainerCompletion, localWorldEvidenceContexts } from './runner-container.js';
|
|
5
|
+
import { LocalWorldJournal } from './runner-world-journal.js';
|
|
6
|
+
/** Offline removal of evidence only. The ORIGINAL owned container must already
|
|
7
|
+
* have an exact no-dispatch/cleanup receipt. No cloud login, model, Docker exec,
|
|
8
|
+
* credential rotation or deletion of source folders/cloud reports occurs.
|
|
9
|
+
*/
|
|
10
|
+
export async function forgetWorldEvidence(operation, options, output) {
|
|
11
|
+
const id = identifier(operation);
|
|
12
|
+
let context;
|
|
13
|
+
for await (const one of localWorldEvidenceContexts(id, options.workspace, undefined, options.signal)) {
|
|
14
|
+
if (context)
|
|
15
|
+
throw new CliError('local_world_erasure_identity', 'Choose one original saved world operation. No files or keys were removed.', 2);
|
|
16
|
+
context = one;
|
|
17
|
+
}
|
|
18
|
+
if (!context)
|
|
19
|
+
throw new CliError('local_world_erasure_identity', 'Choose one original saved world operation. No files or keys were removed.', 2);
|
|
20
|
+
const saved = await localContainerCompletion(context, id, 'world_session');
|
|
21
|
+
if (!saved.worldEvidenceOwner)
|
|
22
|
+
throw new CliError('local_world_erasure_identity', 'The original local world identity is unavailable.');
|
|
23
|
+
if (!options.approve) {
|
|
24
|
+
if (options.json || !process.stdin.isTTY || !process.stderr.isTTY)
|
|
25
|
+
throw new CliError('local_world_erasure_consent', 'Pass --discard-evidence to remove this operation’s private local evidence key and files. Cloud reports are unchanged.', 2);
|
|
26
|
+
const approved = await selectOne('Remove private local evidence for ' + id + '?', [
|
|
27
|
+
{ value: false, label: 'Keep evidence' },
|
|
28
|
+
{ value: true, label: 'Remove key and files', detail: 'Unsynchronized evidence cannot be recovered. Cloud reports and the cleanup receipt remain.' },
|
|
29
|
+
], options.signal);
|
|
30
|
+
if (!approved)
|
|
31
|
+
return output({ state: 'kept', operation_id: id }, 'Local evidence kept.');
|
|
32
|
+
}
|
|
33
|
+
options.signal.throwIfAborted();
|
|
34
|
+
const journal = await LocalWorldJournal.acquire(saved.worldEvidenceOwner, saved.manifestSha256, { existingOnly: true, erasureOnly: true });
|
|
35
|
+
try {
|
|
36
|
+
const receipt = await journal.erase(options.signal);
|
|
37
|
+
output({ ...receipt, operation_id: id, workspace_id: context.workspace }, receipt.already_removed
|
|
38
|
+
? 'This operation’s private local evidence was already removed. Cloud reports are unchanged.'
|
|
39
|
+
: receipt.continuation_required ? 'The private evidence key is removed. A bounded file-removal pass finished; repeat this exact forget command to continue. Cloud reports are unchanged.'
|
|
40
|
+
: 'Private local evidence files and their OS-held key removed. Cloud reports are unchanged. Filesystem backups are not covered.');
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await journal.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function pruneWorldEvidence(options, output) {
|
|
47
|
+
let removed = 0, alreadyRemoved = 0, kept = 0, deferredCount = 0, inspected = 0;
|
|
48
|
+
const deferred = [];
|
|
49
|
+
const problem = (operation, code) => {
|
|
50
|
+
deferredCount++;
|
|
51
|
+
const item = { operation_id: operation, code };
|
|
52
|
+
if (deferred.length < 25)
|
|
53
|
+
deferred.push(item);
|
|
54
|
+
output({ schema: 'firedrill.local-world-prune-item.v1', ...item, state: 'deferred' }, `${operation}: ${code}. Evidence was not removed.`);
|
|
55
|
+
};
|
|
56
|
+
for await (const context of localWorldEvidenceContexts(undefined, options.workspace, problem, options.signal)) {
|
|
57
|
+
options.signal.throwIfAborted();
|
|
58
|
+
let journal;
|
|
59
|
+
try {
|
|
60
|
+
const saved = await localContainerCompletion(context, context.operation, 'world_session');
|
|
61
|
+
if (!saved.worldEvidenceOwner)
|
|
62
|
+
throw new CliError('local_world_erasure_identity', 'Original local world identity unavailable.');
|
|
63
|
+
journal = await LocalWorldJournal.acquire(saved.worldEvidenceOwner, saved.manifestSha256, { existingOnly: true, erasureOnly: true });
|
|
64
|
+
if (!journal.removalStarted && !Number.isFinite(journal.expiresAt))
|
|
65
|
+
throw new CliError('local_world_retention_unavailable', 'Use the exact forget command to remove a partially initialized private journal. No expiry was invented.');
|
|
66
|
+
if (!journal.removalStarted && journal.expiresAt > Date.now()) {
|
|
67
|
+
kept++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const receipt = await journal.erase(options.signal);
|
|
71
|
+
if (receipt.continuation_required)
|
|
72
|
+
problem(context.operation, 'local_world_erasure_pending');
|
|
73
|
+
else if (receipt.already_removed)
|
|
74
|
+
alreadyRemoved++;
|
|
75
|
+
else
|
|
76
|
+
removed++;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!(error instanceof CliError))
|
|
80
|
+
throw error;
|
|
81
|
+
problem(context.operation, error.code);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (journal)
|
|
85
|
+
await journal.close();
|
|
86
|
+
inspected++;
|
|
87
|
+
if (inspected % 250 === 0)
|
|
88
|
+
output({ schema: 'firedrill.local-world-prune-progress.v1', inspected, removed,
|
|
89
|
+
already_removed: alreadyRemoved, kept, deferred_count: deferredCount }, `Checked ${inspected} local world receipts.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
output({ schema: 'firedrill.local-world-prune.v1', removed, already_removed: alreadyRemoved, kept, deferred,
|
|
93
|
+
inspected, deferred_count: deferredCount, deferred_truncated: deferredCount > deferred.length,
|
|
94
|
+
cloud_data_changed: false, filesystem_backup_erasure_attested: false }, `Local evidence: ${removed} removed, ${alreadyRemoved} previously removed, ${kept} within retention, ${deferredCount} need attention. Cloud reports are unchanged.`);
|
|
95
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { api } from './auth.js';
|
|
2
|
+
import { CliError } from './http.js';
|
|
3
|
+
import { localContainerCompletion } from './runner-container.js';
|
|
4
|
+
import { localWorldClaimPath } from './runner-world-claim.js';
|
|
5
|
+
import { LocalWorldCommandEvidence } from './runner-world-command-evidence.js';
|
|
6
|
+
import { historicalRunnerSigner } from './runner-identity.js';
|
|
7
|
+
import { localClosingReceiptMatches } from '../../contracts/local-evidence-contracts.js';
|
|
8
|
+
/** Existing encrypted bytes only, with a finite distinct historical server
|
|
9
|
+
* authority. No container/native channel, lease pulse, runtime configuration,
|
|
10
|
+
* model request or target replay exists in this recovery path.
|
|
11
|
+
*/
|
|
12
|
+
export async function syncLocalWorldEvidence(scope, operation, signal, options = {}) {
|
|
13
|
+
const saved = await localContainerCompletion(scope, operation, 'world_session'), owner = saved.worldEvidenceOwner;
|
|
14
|
+
if (!owner)
|
|
15
|
+
throw new CliError('local_world_evidence_changed', 'The original local world identity is unavailable. No world was replaced.');
|
|
16
|
+
const receipt = await api(localWorldClaimPath(owner) + '/completion', { actor: scope.owner, workspace: scope.workspace, signal });
|
|
17
|
+
if (!localClosingReceiptMatches(receipt, { ...owner, manifestSha256: saved.manifestSha256 })
|
|
18
|
+
|| receipt.schema !== 'firedrill.local-completion-receipt.v1' || receipt.job_id !== owner.job || receipt.attempt_id !== owner.attempt
|
|
19
|
+
|| receipt.lease_id !== owner.lease || receipt.request_key !== owner.request || receipt.manifest_sha256 !== saved.manifestSha256
|
|
20
|
+
|| receipt.received !== true || receipt.cleanup_recorded !== true
|
|
21
|
+
|| receipt.document?.schema !== 'firedrill.local-world-container-receipt.v1'
|
|
22
|
+
|| receipt.document.world_session.session_id !== owner.session.session_id) {
|
|
23
|
+
throw new CliError('local_world_closing_required', 'Synchronize the original cleanup receipt with firedrill runner completion sync before transferring saved world files.');
|
|
24
|
+
}
|
|
25
|
+
const common = { schema: 'firedrill.local-world-evidence-sync.v2', operation_id: owner.lease, session_id: owner.session.session_id,
|
|
26
|
+
cleanup_receipt_received: receipt.received, cloud_settlement_observed: receipt.settled,
|
|
27
|
+
provenance: 'customer_device', command_details: options.onCommand ? 'streamed' : 'summary_only' };
|
|
28
|
+
if (receipt.settled === true)
|
|
29
|
+
return { ...common, state: 'closed', synchronized_commands: null, unavailable_commands: null, command_scan_complete: null };
|
|
30
|
+
if (receipt.settled !== false)
|
|
31
|
+
throw new CliError('local_world_evidence_changed', 'The closing status could not be confirmed. No files were discarded.');
|
|
32
|
+
const signer = await historicalRunnerSigner(scope, saved.device, signal);
|
|
33
|
+
// Cleanup survives revocation, but private evidence does not. Do not even
|
|
34
|
+
// decrypt the retained evidence journal on this cleanup-only recovery path.
|
|
35
|
+
if (signer.cleanupOnly)
|
|
36
|
+
return { ...common, state: 'access_removed', synchronized_commands: null,
|
|
37
|
+
unavailable_commands: null, command_scan_complete: false, native_status: { state: 'unavailable', code: 'runner_cleanup_only' } };
|
|
38
|
+
const evidence = await LocalWorldCommandEvidence.recover(owner, saved.manifestSha256, signal);
|
|
39
|
+
let synchronized = 0, unavailable = 0, commandScanComplete = true;
|
|
40
|
+
let nativeStatus = null;
|
|
41
|
+
const report = async (command, code) => {
|
|
42
|
+
if (code === null)
|
|
43
|
+
synchronized++;
|
|
44
|
+
else
|
|
45
|
+
unavailable++;
|
|
46
|
+
await options.onCommand?.({ schema: 'firedrill.local-world-evidence-command.v1', operation_id: owner.lease,
|
|
47
|
+
session_id: owner.session.session_id, command_id: command.id, sequence: command.sequence,
|
|
48
|
+
state: code === null ? 'synchronized' : 'unavailable', code });
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
for await (const command of evidence.commands()) {
|
|
52
|
+
signal.throwIfAborted();
|
|
53
|
+
const original = await evidence.saved(command);
|
|
54
|
+
if (!original) {
|
|
55
|
+
await report(command, 'native_result_not_retained');
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
await evidence.retain(command, original);
|
|
60
|
+
await report(command, null);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (!(error instanceof CliError) || !['local_world_file_unavailable', 'local_world_evidence_unavailable',
|
|
64
|
+
'local_world_closing_access_removed', 'runner_cleanup_only'].includes(error.code))
|
|
65
|
+
throw error;
|
|
66
|
+
await report(command, error.code);
|
|
67
|
+
if (['local_world_closing_access_removed', 'runner_cleanup_only'].includes(error.code)) {
|
|
68
|
+
commandScanComplete = false;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (saved.worldFinalStatus && commandScanComplete) {
|
|
74
|
+
try {
|
|
75
|
+
nativeStatus = await evidence.retainFinalNative({ size: saved.worldFinalStatus.size, sha256: saved.worldFinalStatus.sha256 });
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (!(error instanceof CliError) || !['local_world_final_unavailable', 'local_world_closing_access_removed', 'runner_cleanup_only'].includes(error.code))
|
|
79
|
+
throw error;
|
|
80
|
+
nativeStatus = { state: 'unavailable', code: error.code };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const partial = !commandScanComplete || unavailable > 0 || !!nativeStatus && typeof nativeStatus === 'object' && 'state' in nativeStatus
|
|
84
|
+
&& nativeStatus.state !== 'retained';
|
|
85
|
+
return { ...common, state: partial ? 'partial' : 'synced', synchronized_commands: synchronized,
|
|
86
|
+
unavailable_commands: unavailable, command_scan_complete: commandScanComplete, native_status: nativeStatus };
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await evidence.close();
|
|
90
|
+
}
|
|
91
|
+
}
|