firedrill-run 2.2.5 → 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,367 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { lstat, mkdir, open, opendir, realpath, link, unlink } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { access, api } from './auth.js';
|
|
6
|
+
import { CliError, endpoint, responseError } from './http.js';
|
|
7
|
+
import { selectOne, terminalText } from './prompts.js';
|
|
8
|
+
import { identifier } from './world-contracts.js';
|
|
9
|
+
import { evidenceManifestMatches, driverEventMatches, timelineMatches } from '../../contracts/evidence-contracts.js';
|
|
10
|
+
const invalid = () => new CliError('evidence_identity_changed', 'The selected evidence could not be confirmed. Nothing was invoked or regraded.');
|
|
11
|
+
const filterKeys = ['kind', 'artifact', 'step', 'attempt', 'from_at', 'to_at'];
|
|
12
|
+
function instant(value) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return null;
|
|
15
|
+
if (value.length > 64 || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/.test(value)
|
|
16
|
+
|| !Number.isFinite(Date.parse(value)))
|
|
17
|
+
throw new CliError('evidence_filter_invalid', 'Use an ISO date and time with seconds and Z or a UTC offset, such as 2026-11-08T18:30:00Z.', 2);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function selectionFor(options, artifact) {
|
|
21
|
+
const attempt = options.evidenceAttempt ?? null;
|
|
22
|
+
if (attempt !== null && (!/^[1-9][0-9]{0,9}$/.test(attempt) || Number(attempt) > 2147483647))
|
|
23
|
+
throw new CliError('evidence_filter_invalid', 'Choose an attempt number between 1 and 2147483647.', 2);
|
|
24
|
+
const from = instant(options.evidenceFrom), to = instant(options.evidenceTo);
|
|
25
|
+
if (from !== null && to !== null && Date.parse(from) > Date.parse(to))
|
|
26
|
+
throw new CliError('evidence_filter_invalid', 'The start time must be before or equal to the end time.', 2);
|
|
27
|
+
return { kind: options.evidenceKind ?? '', artifact, step: options.evidenceStep === undefined ? null : identifier(options.evidenceStep),
|
|
28
|
+
attempt, from_at: from, to_at: to };
|
|
29
|
+
}
|
|
30
|
+
function originalIntent(value, prefix, execution, scope, selection) {
|
|
31
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
32
|
+
throw invalid();
|
|
33
|
+
const row = value;
|
|
34
|
+
if (row.owner !== scope.owner || row.workspace !== scope.workspace || row.execution !== execution || row.prefix !== prefix
|
|
35
|
+
|| typeof row.through_at !== 'string' || row.through_at.length > 64 || !Number.isFinite(Date.parse(row.through_at)))
|
|
36
|
+
throw invalid();
|
|
37
|
+
const common = ['schema', 'owner', 'workspace', 'execution', 'prefix', 'through_at'];
|
|
38
|
+
if (row.schema === 'firedrill.evidence-bundle.v1') {
|
|
39
|
+
// Recover only the original pre-filter selection. Do not rewrite the
|
|
40
|
+
// existing journal or let new filters relabel its already-saved pages.
|
|
41
|
+
const keys = [...common, 'kind', 'artifact'];
|
|
42
|
+
if (Object.keys(row).length !== keys.length || Object.keys(row).some(key => !keys.includes(key))
|
|
43
|
+
|| row.kind !== selection.kind || row.artifact !== selection.artifact
|
|
44
|
+
|| selection.step !== null || selection.attempt !== null || selection.from_at !== null || selection.to_at !== null)
|
|
45
|
+
throw invalid();
|
|
46
|
+
}
|
|
47
|
+
else if (row.schema === 'firedrill.evidence-bundle.v2') {
|
|
48
|
+
const keys = [...common, 'selection'];
|
|
49
|
+
const saved = row.selection;
|
|
50
|
+
if (Object.keys(row).length !== keys.length || Object.keys(row).some(key => !keys.includes(key))
|
|
51
|
+
|| !saved || typeof saved !== 'object' || Array.isArray(saved)
|
|
52
|
+
|| Object.keys(saved).length !== filterKeys.length || Object.keys(saved).some(key => !filterKeys.some(name => name === key))
|
|
53
|
+
|| filterKeys.some(key => saved[key] !== selection[key]))
|
|
54
|
+
throw invalid();
|
|
55
|
+
}
|
|
56
|
+
else
|
|
57
|
+
throw invalid();
|
|
58
|
+
return { schema: 'firedrill.evidence-bundle.v2', ...scope, execution, prefix, through_at: row.through_at, selection };
|
|
59
|
+
}
|
|
60
|
+
function continuation(prefix, execution, scope, selection, page, json) {
|
|
61
|
+
const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
62
|
+
const command = `firedrill report ${prefix.split('/')[5]} drill ${execution} evidence${selection.artifact ? ` ${selection.artifact}` : ''}`
|
|
63
|
+
+ ` --workspace ${scope.workspace} --page ${page}${json ? ' --json' : ''}`;
|
|
64
|
+
return command + (selection.kind ? ` --kind ${quote(selection.kind)}` : '') + (selection.step ? ` --step ${selection.step}` : '')
|
|
65
|
+
+ (selection.attempt ? ` --attempt ${selection.attempt}` : '')
|
|
66
|
+
+ (selection.from_at ? ` --from-at ${quote(selection.from_at)}` : '') + (selection.to_at ? ` --to-at ${quote(selection.to_at)}` : '');
|
|
67
|
+
}
|
|
68
|
+
async function regular(path) {
|
|
69
|
+
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
70
|
+
const stat = await file.stat();
|
|
71
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.mode & 0o077 || process.getuid && stat.uid !== process.getuid()) {
|
|
72
|
+
await file.close();
|
|
73
|
+
throw new CliError('evidence_file_unsafe', 'Use private regular evidence files owned by this account. Symlinks and shared files are refused.');
|
|
74
|
+
}
|
|
75
|
+
return file;
|
|
76
|
+
}
|
|
77
|
+
async function readJSON(path, limit) {
|
|
78
|
+
const file = await regular(path);
|
|
79
|
+
try {
|
|
80
|
+
if ((await file.stat()).size > limit)
|
|
81
|
+
throw invalid();
|
|
82
|
+
return JSON.parse(await file.readFile('utf8'));
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await file.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function writeNew(path, value) {
|
|
89
|
+
const temporary = join(dirname(path), `.evidence-${randomUUID()}.partial`);
|
|
90
|
+
const file = await open(temporary, 'wx', 0o600), stat = await file.stat();
|
|
91
|
+
try {
|
|
92
|
+
await file.writeFile(JSON.stringify(value));
|
|
93
|
+
await file.sync();
|
|
94
|
+
const current = await lstat(temporary);
|
|
95
|
+
if (!current.isFile() || current.ino !== stat.ino || current.dev !== stat.dev)
|
|
96
|
+
throw invalid();
|
|
97
|
+
await link(temporary, path); // Publish complete metadata; never overwrite.
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await file.close();
|
|
101
|
+
const current = await lstat(temporary).catch(() => null);
|
|
102
|
+
if (current?.isFile() && current.ino === stat.ino && current.dev === stat.dev)
|
|
103
|
+
await unlink(temporary);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function privateDirectory(path, create = true) {
|
|
107
|
+
if (create)
|
|
108
|
+
await mkdir(path, { mode: 0o700 }).catch(error => {
|
|
109
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST'))
|
|
110
|
+
throw error;
|
|
111
|
+
});
|
|
112
|
+
const stat = await lstat(path);
|
|
113
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.mode & 0o077 || process.getuid && stat.uid !== process.getuid())
|
|
114
|
+
throw new CliError('evidence_directory_unsafe', 'Choose a private evidence directory owned by this account, not a symlink or shared folder.');
|
|
115
|
+
}
|
|
116
|
+
async function hashFile(file, maximum) {
|
|
117
|
+
const stat = await file.stat();
|
|
118
|
+
if (stat.size > maximum)
|
|
119
|
+
throw new CliError('evidence_checksum_mismatch', 'A partial file is larger than the original evidence. It was not replaced.');
|
|
120
|
+
const hash = createHash('sha256'), buffer = Buffer.alloc(1024 * 1024);
|
|
121
|
+
let position = 0;
|
|
122
|
+
while (position < stat.size) {
|
|
123
|
+
const { bytesRead } = await file.read(buffer, 0, Math.min(buffer.length, stat.size - position), position);
|
|
124
|
+
if (!bytesRead)
|
|
125
|
+
throw invalid();
|
|
126
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
127
|
+
position += bytesRead;
|
|
128
|
+
}
|
|
129
|
+
return { hash, size: position };
|
|
130
|
+
}
|
|
131
|
+
async function download(row, directory, scope, options) {
|
|
132
|
+
if (row.state !== 'available' || !row.download_path || !row.sha256 || !row.generation)
|
|
133
|
+
throw invalid();
|
|
134
|
+
const destination = join(directory, row.filename), partial = join(directory, `.${row.id}.partial`);
|
|
135
|
+
const exists = await lstat(destination).catch(error => { if (error?.code !== 'ENOENT')
|
|
136
|
+
throw error; return null; });
|
|
137
|
+
if (exists) {
|
|
138
|
+
const interrupted = await lstat(partial).catch(error => {
|
|
139
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
|
|
140
|
+
throw error;
|
|
141
|
+
return null;
|
|
142
|
+
});
|
|
143
|
+
if (exists.isFile() && exists.nlink === 2 && interrupted?.isFile() && interrupted.ino === exists.ino && interrupted.dev === exists.dev)
|
|
144
|
+
await unlink(partial); // Recover only our exact already-published inode.
|
|
145
|
+
const finished = await regular(destination);
|
|
146
|
+
try {
|
|
147
|
+
const current = await hashFile(finished, row.size);
|
|
148
|
+
if (current.size !== row.size || current.hash.digest('hex') !== row.sha256)
|
|
149
|
+
throw invalid();
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
await finished.close();
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
let file;
|
|
157
|
+
try {
|
|
158
|
+
file = await open(partial, constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST'))
|
|
162
|
+
throw error;
|
|
163
|
+
file = await open(partial, constants.O_RDWR | constants.O_NOFOLLOW);
|
|
164
|
+
}
|
|
165
|
+
const original = await file.stat();
|
|
166
|
+
try {
|
|
167
|
+
if (!original.isFile() || original.nlink !== 1 || original.mode & 0o077 || process.getuid && original.uid !== process.getuid())
|
|
168
|
+
throw invalid();
|
|
169
|
+
const current = await hashFile(file, row.size);
|
|
170
|
+
let position = current.size;
|
|
171
|
+
const etag = `"${row.sha256}:${row.generation}"`;
|
|
172
|
+
if (position < row.size || row.size === 0)
|
|
173
|
+
await access(async (token) => {
|
|
174
|
+
const start = position;
|
|
175
|
+
const response = await fetch(endpoint(row.download_path), { redirect: 'error', credentials: 'omit',
|
|
176
|
+
signal: AbortSignal.any([options.signal, AbortSignal.timeout(2 * 60 * 60 * 1000)]),
|
|
177
|
+
headers: { Authorization: `Bearer ${token}`, 'X-Firedrill-Actor': scope.owner, 'X-Firedrill-Workspace': scope.workspace,
|
|
178
|
+
'If-Match': etag, ...(start ? { Range: `bytes=${start}-` } : {}) } });
|
|
179
|
+
if (response.status !== (start ? 206 : 200)) {
|
|
180
|
+
await response.body?.cancel();
|
|
181
|
+
throw responseError(null, response.status);
|
|
182
|
+
}
|
|
183
|
+
if (response.headers.get('content-length') !== String(row.size - start) || response.headers.get('etag') !== etag
|
|
184
|
+
|| response.headers.get('x-content-sha256') !== row.sha256 || response.headers.get('x-evidence-generation') !== row.generation
|
|
185
|
+
|| response.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== row.content_type.toLowerCase()
|
|
186
|
+
|| start && response.headers.get('content-range') !== `bytes ${start}-${row.size - 1}/${row.size}` || !response.body) {
|
|
187
|
+
await response.body?.cancel();
|
|
188
|
+
throw invalid();
|
|
189
|
+
}
|
|
190
|
+
const reader = response.body.getReader();
|
|
191
|
+
try {
|
|
192
|
+
while (true) {
|
|
193
|
+
const part = await reader.read();
|
|
194
|
+
if (part.done)
|
|
195
|
+
break;
|
|
196
|
+
if (position + part.value.byteLength > row.size)
|
|
197
|
+
throw invalid();
|
|
198
|
+
let offset = 0;
|
|
199
|
+
while (offset < part.value.byteLength) {
|
|
200
|
+
const written = await file.write(part.value, offset, part.value.byteLength - offset, position);
|
|
201
|
+
if (!written.bytesWritten)
|
|
202
|
+
throw new CliError('evidence_disk_unavailable', 'The evidence file could not be written. Free disk space, then resume the same bundle.');
|
|
203
|
+
current.hash.update(part.value.subarray(offset, offset + written.bytesWritten));
|
|
204
|
+
offset += written.bytesWritten;
|
|
205
|
+
position += written.bytesWritten;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
await reader.cancel().catch(() => undefined);
|
|
211
|
+
reader.releaseLock();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
if (position !== row.size || current.hash.digest('hex') !== row.sha256)
|
|
215
|
+
throw new CliError('evidence_checksum_mismatch', 'The evidence is incomplete or its checksum does not match. The partial file is preserved for inspection, not published as complete.');
|
|
216
|
+
await file.sync();
|
|
217
|
+
const latest = await lstat(partial);
|
|
218
|
+
if (!latest.isFile() || latest.ino !== original.ino || latest.dev !== original.dev || latest.size !== row.size)
|
|
219
|
+
throw invalid();
|
|
220
|
+
await link(partial, destination);
|
|
221
|
+
await unlink(partial);
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
await file.close();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function manifest(prefix, execution, scope, options, page, selection, boundary) {
|
|
228
|
+
const query = new URLSearchParams({ page: String(page), page_size: '100', kind: selection.kind });
|
|
229
|
+
if (boundary)
|
|
230
|
+
query.set('through_at', boundary);
|
|
231
|
+
for (const key of ['artifact', 'step', 'attempt', 'from_at', 'to_at']) {
|
|
232
|
+
const value = selection[key];
|
|
233
|
+
if (value !== null)
|
|
234
|
+
query.set(key, value);
|
|
235
|
+
}
|
|
236
|
+
const result = await api(`${prefix}/evidence?${query}`, { actor: scope.owner, workspace: scope.workspace, signal: options.signal });
|
|
237
|
+
if (!evidenceManifestMatches(result, prefix, execution) || result.page !== page || boundary && result.through_at !== boundary)
|
|
238
|
+
throw invalid();
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
async function readOrDownload(prefix, execution, selection, scope, interactive, options, output) {
|
|
242
|
+
const artifact = selection ? identifier(selection) : null;
|
|
243
|
+
const selected = selectionFor(options, artifact);
|
|
244
|
+
if (artifact && options.page !== undefined && options.page !== '1')
|
|
245
|
+
throw new CliError('arguments_invalid', 'An individual evidence file has one manifest page. Omit --page or use --page 1.', 2);
|
|
246
|
+
if (!options.into) {
|
|
247
|
+
if (options.approveDownload)
|
|
248
|
+
throw new CliError('arguments_invalid', 'Choose --into <private-directory> before approving an evidence download.', 2);
|
|
249
|
+
const value = await manifest(prefix, execution, scope, options, Number(options.page ?? 1), selected);
|
|
250
|
+
output(value, [...value.results.map(row => `${row.id} · ${row.kind} · ${row.state} · ${row.size.toLocaleString()} bytes`),
|
|
251
|
+
`Page ${value.page}; ${value.count.toLocaleString()} files.`,
|
|
252
|
+
...(value.next ? [`Next: ${continuation(prefix, execution, scope, selected, value.page + 1, options.json)}`] : []),
|
|
253
|
+
'Download with the same filters, omit --page and add --into <private-directory>. Repeat it to resume an interrupted bundle.'].join('\n'));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (options.page !== undefined)
|
|
257
|
+
throw new CliError('arguments_invalid', 'A bundle streams all selected manifest pages. Omit --page when downloading.', 2);
|
|
258
|
+
if (!['linux', 'darwin'].includes(process.platform) || !constants.O_NOFOLLOW)
|
|
259
|
+
throw new CliError('evidence_filesystem_unsupported', 'Private resumable evidence bundles currently require Linux or macOS file-isolation support. Individual authenticated downloads remain available in the portal.', 2);
|
|
260
|
+
if (!options.approveDownload && (!interactive || !await selectOne('Save private evidence on this device?', [
|
|
261
|
+
{ value: false, label: 'Cancel' }, { value: true, label: 'Download evidence', detail: 'Selected conversations, captures, state and traces. Existing files are never replaced.' }
|
|
262
|
+
], options.signal)))
|
|
263
|
+
throw new CliError('evidence_download_consent_required', 'Use --approve-download to save the selected private evidence in non-interactive mode.', 2);
|
|
264
|
+
const chosen = resolve(options.into), parent = await realpath(dirname(chosen)), directory = join(parent, basename(chosen));
|
|
265
|
+
await privateDirectory(directory);
|
|
266
|
+
const intentPath = join(directory, 'manifest.json');
|
|
267
|
+
let intent = null;
|
|
268
|
+
try {
|
|
269
|
+
intent = originalIntent(await readJSON(intentPath, 16384), prefix, execution, scope, selected);
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
if (!intent) {
|
|
276
|
+
const entries = await opendir(directory);
|
|
277
|
+
try {
|
|
278
|
+
if (await entries.read())
|
|
279
|
+
throw new CliError('evidence_directory_not_empty', 'Choose an empty private directory or the original evidence bundle directory.', 2);
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
await entries.close();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
let page = await manifest(prefix, execution, scope, options, 1, selected, intent?.through_at);
|
|
286
|
+
if (!intent) {
|
|
287
|
+
intent = { schema: 'firedrill.evidence-bundle.v2', ...scope, execution, prefix, through_at: page.through_at, selection: selected };
|
|
288
|
+
await writeNew(intentPath, intent);
|
|
289
|
+
}
|
|
290
|
+
await privateDirectory(join(directory, 'pages'));
|
|
291
|
+
await privateDirectory(join(directory, 'files'));
|
|
292
|
+
let completed = 0, unavailable = 0;
|
|
293
|
+
while (true) {
|
|
294
|
+
options.signal.throwIfAborted();
|
|
295
|
+
const pagePath = join(directory, 'pages', `page-${page.page}.json`);
|
|
296
|
+
const exists = await lstat(pagePath).catch(error => { if (error?.code !== 'ENOENT')
|
|
297
|
+
throw error; return null; });
|
|
298
|
+
if (exists) {
|
|
299
|
+
const retained = await readJSON(pagePath, 2 * 1024 ** 2);
|
|
300
|
+
if (!evidenceManifestMatches(retained, prefix, execution) || retained.page !== page.page || retained.through_at !== intent.through_at
|
|
301
|
+
|| retained.results.length !== page.results.length || retained.results.some((old, index) => {
|
|
302
|
+
const current = page.results[index];
|
|
303
|
+
return old.id !== current.id || old.generation !== current.generation || old.sha256 !== current.sha256 || old.size !== current.size;
|
|
304
|
+
}))
|
|
305
|
+
throw invalid();
|
|
306
|
+
}
|
|
307
|
+
else
|
|
308
|
+
await writeNew(pagePath, page);
|
|
309
|
+
for (const row of page.results) {
|
|
310
|
+
if (row.state !== 'available') {
|
|
311
|
+
unavailable++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
process.stderr.write(`Evidence ${terminalText(row.id)} · ${row.size.toLocaleString()} bytes\n`);
|
|
315
|
+
await download(row, join(directory, 'files'), scope, options);
|
|
316
|
+
completed++;
|
|
317
|
+
}
|
|
318
|
+
if (!page.next)
|
|
319
|
+
break;
|
|
320
|
+
page = await manifest(prefix, execution, scope, options, page.page + 1, selected, intent.through_at);
|
|
321
|
+
}
|
|
322
|
+
output({ schema: 'firedrill.evidence-bundle-receipt.v1', execution_id: execution, completed_files: completed,
|
|
323
|
+
unavailable_files: unavailable, through_at: intent.through_at, directory }, `${completed.toLocaleString()} evidence files saved with verified SHA256.${unavailable ? ` ${unavailable.toLocaleString()} files are unavailable; their manifest entries are retained.` : ''}\n${directory}`);
|
|
324
|
+
}
|
|
325
|
+
export async function evidenceCommand(prefix, execution, selection, scope, interactive, options, output) {
|
|
326
|
+
try {
|
|
327
|
+
await readOrDownload(prefix, execution, selection, scope, interactive, options, output);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
if (error instanceof CliError)
|
|
331
|
+
throw error;
|
|
332
|
+
if (options.signal.aborted)
|
|
333
|
+
throw new CliError('canceled', 'Evidence download stopped. Repeat the same command and directory to resume retained partial files.', 130);
|
|
334
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOSPC')
|
|
335
|
+
throw new CliError('evidence_disk_capacity', 'There is not enough disk space. Free space, then resume the same evidence bundle. No existing file was replaced.');
|
|
336
|
+
throw new CliError('evidence_download_interrupted', 'Evidence download could not finish. Partial files are preserved. Repeat the same command and directory after restoring access or connectivity.');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
export async function timelineCommand(prefix, execution, event, scope, options, output) {
|
|
340
|
+
if (options.into || options.approveDownload)
|
|
341
|
+
throw new CliError('arguments_invalid', 'Use evidence --into <directory> to download original files.', 2);
|
|
342
|
+
if (options.evidenceStep !== undefined)
|
|
343
|
+
throw new CliError('arguments_invalid', 'Use --step with evidence files. Inspect a driver step with timeline <driver-step-id>.', 2);
|
|
344
|
+
const selected = selectionFor(options, null);
|
|
345
|
+
if (event) {
|
|
346
|
+
if (options.page !== undefined || options.evidenceKind !== undefined || selected.attempt !== null || selected.from_at !== null || selected.to_at !== null)
|
|
347
|
+
throw new CliError('arguments_invalid', 'A driver-step detail does not accept list filters. Omit its ID to filter the timeline.', 2);
|
|
348
|
+
const id = identifier(event), row = await api(`${prefix}/timeline/driver/${id}`, { actor: scope.owner, workspace: scope.workspace, signal: options.signal });
|
|
349
|
+
if (!driverEventMatches(row, prefix, execution, id))
|
|
350
|
+
throw invalid();
|
|
351
|
+
output(row, `${terminalText(row.summary)}\n${row.inline_available ? JSON.stringify(row.content, null, 2) : 'Download the original evidence file to inspect this large observation.'}`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const query = new URLSearchParams({ page: options.page ?? '1', category: options.evidenceKind ?? '' });
|
|
355
|
+
for (const key of ['attempt', 'from_at', 'to_at']) {
|
|
356
|
+
const value = selected[key];
|
|
357
|
+
if (value !== null)
|
|
358
|
+
query.set(key, value);
|
|
359
|
+
}
|
|
360
|
+
const value = await api(`${prefix}/timeline?${query}`, { actor: scope.owner, workspace: scope.workspace, signal: options.signal });
|
|
361
|
+
if (!timelineMatches(value, prefix, execution))
|
|
362
|
+
throw invalid();
|
|
363
|
+
const campaign = identifier(prefix.split('/')[5]);
|
|
364
|
+
output(value, [...value.results.map(row => `${row.observed_at === null ? row.recorded_at + ' retained' : new Date(row.observed_at * 1000).toISOString()} · ${row.category === 'service' ? 'service request' : row.category}${row.origin === 'customer_device' ? ' · local device' : ''}\n${terminalText(row.label)}\n${row.id}`
|
|
365
|
+
+ (row.category === 'service' ? `\nInspect original receipt: firedrill report ${campaign} drill ${execution} requests ${row.id} --workspace ${scope.workspace}` : '')),
|
|
366
|
+
`Page ${value.page}; ${value.count.toLocaleString()} observations.`].join('\n\n'));
|
|
367
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { api } from './auth.js';
|
|
2
|
+
import { CliError } from './http.js';
|
|
3
|
+
import { identifier } from './world-contracts.js';
|
|
4
|
+
import { isExecutionProposal, executionPlanMatches } from '../../contracts/director-execution.js';
|
|
5
|
+
export async function executionProposal(context, id, signal) {
|
|
6
|
+
const key = identifier(id);
|
|
7
|
+
const value = await api(`${context.prefix}/execution-proposals/${key}`, { actor: context.owner, workspace: context.workspace, signal });
|
|
8
|
+
if (!isExecutionProposal(value) || value.id !== key || value.actor_id !== context.owner
|
|
9
|
+
|| value.world_id !== context.world || value.plan_request_key !== key
|
|
10
|
+
|| value.plan && !executionPlanMatches(value, { ...value.plan, current: true })) {
|
|
11
|
+
throw new CliError('execution_proposal_unconfirmed', 'This execution proposal could not be confirmed for this account and world. No work was started.');
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function executableProposal(value) {
|
|
16
|
+
if (!value.available || !value.current || !value.can_execute)
|
|
17
|
+
throw new CliError('execution_proposal_changed', 'The proposed inputs or execution permission changed. Inspect the current world. No replacement run was created.', 2);
|
|
18
|
+
}
|
|
19
|
+
export function proposalBasis(value, world, setup) {
|
|
20
|
+
executableProposal(value);
|
|
21
|
+
if (value.source_id !== world.basis.source_id || value.world_revision !== world.basis.world_revision
|
|
22
|
+
|| value.configuration_revision !== world.basis.config_revision
|
|
23
|
+
|| setup && (value.world_revision !== setup.world_revision || value.configuration_revision !== setup.configuration_revision
|
|
24
|
+
|| value.selection_revision !== setup.selection_revision || value.evaluation_revision !== setup.evaluation_revision
|
|
25
|
+
|| (value.plan ? !executionPlanMatches(value, value.plan) : (setup.plan?.id ?? null) !== value.previous_plan_id))) {
|
|
26
|
+
throw new CliError('execution_proposal_changed', 'Source, preparation, configuration, scenarios, criteria or the saved plan changed. Review a new proposal before executing.', 2);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function executionProposalText(value) {
|
|
30
|
+
return `${value.title}\n${value.action === 'validate' ? 'Startup validation' : `${value.scenario_count} scenarios, ${value.repetitions} repetitions, ${value.planned_trials} drills; up to ${value.parallelism} concurrent; ${value.trial_seconds}s per drill`}.\n`
|
|
31
|
+
+ `World revision ${value.world_revision}, configuration revision ${value.configuration_revision}. `
|
|
32
|
+
+ `${value.location === 'choose' ? 'Choose execution location during review.' : `Proposed location: ${value.location}.`}\n`
|
|
33
|
+
+ (value.campaign ? `Original simulation ${value.campaign.id}: ${value.campaign.state}. No new admission will be created.`
|
|
34
|
+
: value.validation ? `Original validation ${value.validation.id}: ${value.validation.state}. It will not be restarted.`
|
|
35
|
+
: 'Proposal only. Execution, real-account access, evidence sharing and simulation funding still require review.');
|
|
36
|
+
}
|