@cassiomc1/forgeloop 1.13.0 → 2.0.0-rc.2
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/AGENT_COMPATIBILITY.md +8 -0
- package/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +37 -4
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +10 -6
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sec-code-eng.md +7 -7
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/EXECUTION_STATE.md +12 -0
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_ENGINEERING.md +28 -2
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/README.md +63 -59
- package/TERMINOLOGY.md +2 -0
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +29 -3
- package/completions/forgeloop.bash +55 -3
- package/completions/forgeloop.fish +183 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +74 -39
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +171 -35
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +512 -8
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +34 -11
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +46 -18
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +9 -0
- package/docs/RELEASE_CHECKLIST.md +116 -12
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +198 -1
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +774 -16
- package/docs/protocol-requirements.json +24 -0
- package/package.json +43 -11
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/task-bundle.schema.json +7 -2
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +6 -6
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +6 -3
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +316 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +289 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/temporary-paths.js +18 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +218 -174
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +192 -0
- package/src/commands/complete.js +19 -3
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +266 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +51 -13
- package/src/commands/gate-record.js +194 -0
- package/src/commands/gate-revalidate.js +135 -0
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +40 -0
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +41 -0
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +39 -15
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +83 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit-ux.js +514 -0
- package/src/core/audit.js +6 -1
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +321 -0
- package/src/core/cli-command-definitions.js +350 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +274 -108
- package/src/core/command-input.js +136 -44
- package/src/core/completion-artifacts.js +85 -34
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +64 -15
- package/src/core/config.js +3 -0
- package/src/core/conformance.js +2 -1
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +656 -0
- package/src/core/contract-revision.js +209 -0
- package/src/core/decision/artifact.js +70 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +104 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +138 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +181 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +35 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/error-codes.js +278 -0
- package/src/core/events.js +535 -147
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +36 -13
- package/src/core/execution-profile.js +63 -38
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +125 -0
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +37 -4
- package/src/core/integration-resources.js +119 -87
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +131 -43
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +9 -4
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +56 -9
- package/src/core/phase.js +266 -103
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +89 -62
- package/src/core/preflight-loaders.js +48 -9
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +68 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +281 -67
- package/src/core/recovery-history.js +67 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +28 -4
- package/src/core/router.js +67 -1
- package/src/core/runtime-context.js +123 -61
- package/src/core/schema-validation.js +11 -3
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/sources.js +37 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +249 -15
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +133 -102
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +23 -8
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +94 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +164 -105
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -287
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
- package/src/repository-index/binary-manager.js +7 -5
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +663 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
|
|
3
4
|
import { canonicalFingerprint } from "./artifacts.js";
|
|
@@ -8,7 +9,8 @@ import { assertRevisionProvider, resolveRevisionProvider } from "./revision/prov
|
|
|
8
9
|
import { REVISION_PROVIDERS } from "./revision/registry.js";
|
|
9
10
|
import { assertSigningProvider, resolveSigningProvider } from "./signing/provider.js";
|
|
10
11
|
import { SIGNING_PROVIDERS } from "./signing/registry.js";
|
|
11
|
-
import {
|
|
12
|
+
import { taskAttestationStatementPath } from "./task-paths.js";
|
|
13
|
+
import { resolveAttestationBundlePath } from "./attachment-paths.js";
|
|
12
14
|
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
13
15
|
import { E_ATTESTATION_CONFIGURATION_INVALID } from "./error-codes.js";
|
|
14
16
|
|
|
@@ -130,6 +132,23 @@ export async function verifyAttestation({
|
|
|
130
132
|
signingProvider = null,
|
|
131
133
|
trustedRoot = null,
|
|
132
134
|
requireSignature = false,
|
|
135
|
+
} = {}) {
|
|
136
|
+
const options = { target, packageRoot, taskId, revision, bundlePath, identity, issuer, revisionProvider, signingProvider, trustedRoot, requireSignature };
|
|
137
|
+
return withProjectReadSnapshot(target, () => verifySelectedAttestation(options));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function verifySelectedAttestation({
|
|
141
|
+
target,
|
|
142
|
+
packageRoot = getPackageRoot(),
|
|
143
|
+
taskId,
|
|
144
|
+
revision = null,
|
|
145
|
+
bundlePath = null,
|
|
146
|
+
identity = null,
|
|
147
|
+
issuer = null,
|
|
148
|
+
revisionProvider = null,
|
|
149
|
+
signingProvider = null,
|
|
150
|
+
trustedRoot = null,
|
|
151
|
+
requireSignature = false,
|
|
133
152
|
} = {}) {
|
|
134
153
|
let effectiveRequireSignature = requireSignature === true;
|
|
135
154
|
let effectiveSigningProvider = signingProvider;
|
|
@@ -146,7 +165,6 @@ export async function verifyAttestation({
|
|
|
146
165
|
} catch (error) {
|
|
147
166
|
if (error.code !== "ARTIFACT_MISSING") configurationError = error;
|
|
148
167
|
}
|
|
149
|
-
if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
|
|
150
168
|
const result = {
|
|
151
169
|
schemaVersion: 1,
|
|
152
170
|
status: "INVALID",
|
|
@@ -166,6 +184,7 @@ export async function verifyAttestation({
|
|
|
166
184
|
return result;
|
|
167
185
|
}
|
|
168
186
|
try {
|
|
187
|
+
if (effectiveBundlePath || effectiveRequireSignature) effectiveBundlePath = resolveAttestationBundlePath(target, taskId, effectiveBundlePath);
|
|
169
188
|
const statementArtifact = await readAttestationStatement({ target, packageRoot, taskId });
|
|
170
189
|
const manifestArtifact = await readCodeManifest({ target, packageRoot, taskId });
|
|
171
190
|
await validateAttestationStatement(statementArtifact.value, packageRoot);
|
package/src/core/attestation.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withTaskTransaction } from "./transaction.js";
|
|
2
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
1
3
|
import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
2
4
|
import { assertSchema, readSchema } from "./schema-validation.js";
|
|
3
5
|
import { assertSecretFree } from "./receipt.js";
|
|
@@ -6,10 +8,11 @@ import { readCodeManifest, validateCodeManifestBindings } from "./code-manifest.
|
|
|
6
8
|
import {
|
|
7
9
|
taskAttestationStatementPath,
|
|
8
10
|
taskAttestationStatementHistoryPath,
|
|
9
|
-
taskAttestationBundlePath,
|
|
10
11
|
} from "./task-paths.js";
|
|
11
12
|
import { appendProtocolEvent } from "./events.js";
|
|
12
13
|
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
14
|
+
import { operationalArtifactExists } from "../storage/operational-context.js";
|
|
15
|
+
import { resolveAttestationBundlePath } from "./attachment-paths.js";
|
|
13
16
|
import { E_ATTESTATION_CONFIGURATION_INVALID } from "./error-codes.js";
|
|
14
17
|
|
|
15
18
|
const STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
|
|
@@ -136,8 +139,13 @@ export async function readAttestationStatement({ target, packageRoot = getPackag
|
|
|
136
139
|
|
|
137
140
|
export async function writeAttestationStatement({ target, packageRoot = getPackageRoot(), taskId, statement } = {}) {
|
|
138
141
|
await validateAttestationStatement(statement, packageRoot);
|
|
142
|
+
return withTaskTransaction({ target, taskId, packageRoot, operation: "attestation-create" }, () =>
|
|
143
|
+
writeValidatedAttestationStatement({ target, packageRoot, taskId, statement }));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function writeValidatedAttestationStatement({ target, packageRoot, taskId, statement }) {
|
|
139
147
|
const relativePath = taskAttestationStatementPath(taskId);
|
|
140
|
-
if (await fileExists(ensureWithin(target, relativePath))) {
|
|
148
|
+
if (operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath))) {
|
|
141
149
|
let previous;
|
|
142
150
|
try {
|
|
143
151
|
previous = await readAttestationStatement({ target, packageRoot, taskId });
|
|
@@ -150,7 +158,7 @@ export async function writeAttestationStatement({ target, packageRoot = getPacka
|
|
|
150
158
|
throw attestationError("E_ATTESTATION_STATEMENT_INVALID", "Attestation statement is immutable within a verification cycle", [relativePath]);
|
|
151
159
|
}
|
|
152
160
|
const historyPath = taskAttestationStatementHistoryPath(taskId, previousCycle);
|
|
153
|
-
if (!(await fileExists(ensureWithin(target, historyPath)))) {
|
|
161
|
+
if (!(operationalArtifactExists(target, historyPath) ?? await fileExists(ensureWithin(target, historyPath)))) {
|
|
154
162
|
await writeJsonArtifact(target, historyPath, previous.value, "in-toto-statement", packageRoot, { taskId, operation: "attestation-statement-history" });
|
|
155
163
|
}
|
|
156
164
|
}
|
|
@@ -164,7 +172,11 @@ export async function writeAttestationStatement({ target, packageRoot = getPacka
|
|
|
164
172
|
return { path: relativePath, fingerprint: artifact.fingerprint, statement };
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
export async function resolveAttestationStatus(
|
|
175
|
+
export async function resolveAttestationStatus(options = {}) {
|
|
176
|
+
return withProjectReadSnapshot(options.target, () => resolveSelectedAttestationStatus(options));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function resolveSelectedAttestationStatus({ target, packageRoot = getPackageRoot(), taskId, revisionProvider = null, bundlePath = null, requireSignature = false, signingProvider = null, signerPolicy = {} } = {}) {
|
|
168
180
|
const result = {
|
|
169
181
|
taskId,
|
|
170
182
|
status: "MISSING",
|
|
@@ -192,7 +204,6 @@ export async function resolveAttestationStatus({ target, packageRoot = getPackag
|
|
|
192
204
|
...(configuredSigning.policy ?? {}),
|
|
193
205
|
...signerPolicy,
|
|
194
206
|
};
|
|
195
|
-
if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
|
|
196
207
|
result.signature = effectiveRequireSignature ? "REQUIRED" : "NOT_CHECKED";
|
|
197
208
|
if ((config.attestation?.mode ?? "off") === "off" && !bundlePath && !effectiveRequireSignature) {
|
|
198
209
|
result.status = "DISABLED";
|
|
@@ -208,8 +219,8 @@ export async function resolveAttestationStatus({ target, packageRoot = getPackag
|
|
|
208
219
|
}
|
|
209
220
|
}
|
|
210
221
|
result.signature = effectiveRequireSignature ? "REQUIRED" : "NOT_CHECKED";
|
|
211
|
-
if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
|
|
212
222
|
try {
|
|
223
|
+
if (effectiveBundlePath || effectiveRequireSignature) effectiveBundlePath = resolveAttestationBundlePath(target, taskId, effectiveBundlePath);
|
|
213
224
|
const statement = await readAttestationStatement({ target, packageRoot, taskId });
|
|
214
225
|
const manifest = await readCodeManifest({ target, packageRoot, taskId });
|
|
215
226
|
const provider = revisionProvider ?? manifest.value.capture.revisionProvider;
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { runNext } from "../commands/next.js";
|
|
2
|
+
import { runReport } from "../commands/report.js";
|
|
3
|
+
import { runStatus } from "../commands/status.js";
|
|
4
|
+
import { evaluateAudit } from "./audit.js";
|
|
5
|
+
import { listApprovals } from "./approvals.js";
|
|
6
|
+
import { buildTaskHistory } from "./history.js";
|
|
7
|
+
import { INTEGRATION_LIMITS } from "./integration-limits.js";
|
|
8
|
+
import { resolveTaskClaimState } from "./task-claim-state.js";
|
|
9
|
+
import { findTaskById } from "./task-discovery.js";
|
|
10
|
+
import { buildTaskTrace } from "./trace.js";
|
|
11
|
+
|
|
12
|
+
export const AUDIT_UX_SCHEMA_VERSION = 1;
|
|
13
|
+
|
|
14
|
+
export const AUDIT_UX_CATEGORIES = Object.freeze([
|
|
15
|
+
"LIFECYCLE",
|
|
16
|
+
"CONTRACT",
|
|
17
|
+
"ROUTING",
|
|
18
|
+
"GATE",
|
|
19
|
+
"CHECK",
|
|
20
|
+
"DIAGNOSTIC",
|
|
21
|
+
"ACTION",
|
|
22
|
+
"APPROVAL",
|
|
23
|
+
"OWNERSHIP",
|
|
24
|
+
"COMPLETION",
|
|
25
|
+
"RECOVERY",
|
|
26
|
+
"INTEGRITY",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const AUDIT_UX_LIMITS = Object.freeze({
|
|
30
|
+
maxTimelineItems: 200,
|
|
31
|
+
maxChecks: 64,
|
|
32
|
+
maxAttemptsPerCheck: 32,
|
|
33
|
+
maxDiagnostics: 64,
|
|
34
|
+
maxActions: 64,
|
|
35
|
+
maxApprovals: 64,
|
|
36
|
+
maxStringLength: 512,
|
|
37
|
+
maxOutputBytes: 256 * 1024,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const PHASES = new Set([
|
|
41
|
+
"RECEIVED", "DISCOVERING", "CONTRACT_READY", "ROUTED", "DESIGNING", "PLANNED",
|
|
42
|
+
"EXECUTING", "VERIFYING", "DIAGNOSING", "CORRECTING", "REVIEWING", "COMPLETE",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const SAFE_STATUSES = new Set([
|
|
46
|
+
"PASSED", "FAILED", "BLOCKED", "PENDING", "CURRENT", "STALE", "VALID", "INVALID",
|
|
47
|
+
"COMPLETE", "INCOMPLETE", "NOT_VERIFIED", "NOT_APPLICABLE", "UNKNOWN",
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const EVENT_TITLES = Object.freeze({
|
|
51
|
+
TASK_RECEIVED: "Task received",
|
|
52
|
+
CONTRACT_VALIDATED: "Contract validated",
|
|
53
|
+
ROUTE_VALIDATED: "Route validated",
|
|
54
|
+
GATE_SATISFIED: "Gate satisfied",
|
|
55
|
+
PREFLIGHT_READY: "Preflight READY",
|
|
56
|
+
PREFLIGHT_BLOCKED: "Preflight BLOCKED",
|
|
57
|
+
DESIGN_GATE_STARTED: "Design phase started",
|
|
58
|
+
PLAN_RECORDED: "Plan recorded",
|
|
59
|
+
EXECUTION_STARTED: "Execution started",
|
|
60
|
+
VERIFICATION_STARTED: "Verification started",
|
|
61
|
+
VERIFICATION_RECORDED: "Verification recorded",
|
|
62
|
+
CHECK_RECORDED: "Check recorded",
|
|
63
|
+
TERMINAL_RESULT_RECORDED: "Terminal result recorded",
|
|
64
|
+
REVIEW_STARTED: "Review started",
|
|
65
|
+
DIAGNOSIS_RECORDED: "Diagnosis recorded",
|
|
66
|
+
DIAGNOSTIC_CASE_RECORDED: "Diagnostic case recorded",
|
|
67
|
+
HYPOTHESIS_DISPOSITION_RECORDED: "Hypothesis disposition recorded",
|
|
68
|
+
INTERVENTION_RECORDED: "Intervention recorded",
|
|
69
|
+
CONTINUITY_RECORDED: "Continuity recorded",
|
|
70
|
+
CHECKPOINT_RECONCILED: "Checkpoint reconciled",
|
|
71
|
+
TASK_RECOVERY_RECORDED: "Recovery recorded",
|
|
72
|
+
TASK_RECOVERY_RESUMED: "Recovery resumed",
|
|
73
|
+
OPERATOR_RECOVERY_RECORDED: "Operator recovery recorded",
|
|
74
|
+
LEGACY_RECOVERY_MIGRATION_RECORDED: "Legacy recovery migration recorded",
|
|
75
|
+
COMPLETION_VALIDATED: "Completion validated",
|
|
76
|
+
COMPLETION_REJECTED: "Completion rejected",
|
|
77
|
+
TRANSACTION_COMMITTED: "Transaction committed",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function auditUxError(code, message) {
|
|
81
|
+
const error = new Error(message);
|
|
82
|
+
error.code = code;
|
|
83
|
+
return error;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function boundedText(value, fallback = null) {
|
|
87
|
+
if (typeof value !== "string") return fallback;
|
|
88
|
+
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
89
|
+
if (!normalized) return fallback;
|
|
90
|
+
return normalized.length > AUDIT_UX_LIMITS.maxStringLength
|
|
91
|
+
? `${normalized.slice(0, AUDIT_UX_LIMITS.maxStringLength - 1)}…`
|
|
92
|
+
: normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const URL_USERINFO_PATTERN = /\b([A-Za-z][A-Za-z0-9+.-]*):\/\/[^/\s@?#]+@/gu;
|
|
96
|
+
const COOKIE_HEADER_PATTERN = /\b(Cookie|Set-Cookie)\s*:\s*[^\r\n]*/giu;
|
|
97
|
+
const AUTHORIZATION_HEADER_PATTERN = /\b(Authorization|Proxy-Authorization)\s*:\s*[^\r\n]*/giu;
|
|
98
|
+
const CREDENTIAL_ASSIGNMENT_PATTERN = /\b(password|passwd|token|access_token|refresh_token|secret|client_secret|api_key|apikey)\s*=\s*[^\s,;]+/giu;
|
|
99
|
+
const ENVIRONMENT_PATTERN = /\b(?!(?:PASSWORD|PASSWD|TOKEN|ACCESS_TOKEN|REFRESH_TOKEN|SECRET|CLIENT_SECRET|API_KEY|APIKEY)\b)[A-Z_][A-Z0-9_]{2,}=[^\s,;]+/gu;
|
|
100
|
+
const FILE_URL_PATTERN = /\bfile:\/\/[^\s,;)]*/giu;
|
|
101
|
+
const WINDOWS_PATH_PATTERN = /(?:\b[A-Za-z]:[\\/]|\\\\)[^\s,;)]*/gu;
|
|
102
|
+
const POSIX_PATH_PATTERN = /(^|[^\w])\/(?:[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*)/gu;
|
|
103
|
+
const URL_PATTERN = /\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s,;)]*/gu;
|
|
104
|
+
|
|
105
|
+
function safeText(value, fallback = null) {
|
|
106
|
+
if (typeof value !== "string") return fallback;
|
|
107
|
+
const bounded = value.length > AUDIT_UX_LIMITS.maxStringLength
|
|
108
|
+
? `${value.slice(0, AUDIT_UX_LIMITS.maxStringLength - 1)}…`
|
|
109
|
+
: value;
|
|
110
|
+
const text = bounded.trim();
|
|
111
|
+
if (!text) return fallback;
|
|
112
|
+
let redacted = text
|
|
113
|
+
.replace(URL_USERINFO_PATTERN, "$1://<credential>@")
|
|
114
|
+
.replace(COOKIE_HEADER_PATTERN, "$1: <credential>")
|
|
115
|
+
.replace(AUTHORIZATION_HEADER_PATTERN, "$1: <credential>")
|
|
116
|
+
.replace(CREDENTIAL_ASSIGNMENT_PATTERN, "$1=<credential>")
|
|
117
|
+
.replace(ENVIRONMENT_PATTERN, "<environment>")
|
|
118
|
+
.replace(FILE_URL_PATTERN, "<path>")
|
|
119
|
+
.replace(WINDOWS_PATH_PATTERN, "<path>");
|
|
120
|
+
|
|
121
|
+
const urls = [];
|
|
122
|
+
redacted = redacted.replace(URL_PATTERN, (url) => {
|
|
123
|
+
const marker = `__AUDIT_UX_URL_${urls.length}__`;
|
|
124
|
+
urls.push(url);
|
|
125
|
+
return marker;
|
|
126
|
+
});
|
|
127
|
+
redacted = redacted.replace(POSIX_PATH_PATTERN, "$1<path>");
|
|
128
|
+
return boundedText(
|
|
129
|
+
redacted.replace(/__AUDIT_UX_URL_(\d+)__/gu, (_, index) => urls[Number(index)]),
|
|
130
|
+
fallback,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function safeIdentifier(value) {
|
|
135
|
+
const text = boundedText(value);
|
|
136
|
+
return text && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u.test(text) ? text : null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function safeStatus(value) {
|
|
140
|
+
const normalized = typeof value === "string" ? value.toUpperCase() : "UNKNOWN";
|
|
141
|
+
return SAFE_STATUSES.has(normalized) ? normalized : "UNKNOWN";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function safePhase(value) {
|
|
145
|
+
return typeof value === "string" && PHASES.has(value) ? value : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function uniqueStrings(values) {
|
|
149
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value))].sort();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function projectErrors(errors = []) {
|
|
153
|
+
return errors.map((error) => ({
|
|
154
|
+
code: safeIdentifier(error?.code) ?? "E_AUDIT_UX_INVALID_ERROR",
|
|
155
|
+
message: safeText(error?.message, "Canonical validation reported an error."),
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function categoryForEvent(event) {
|
|
160
|
+
const type = event?.type ?? "";
|
|
161
|
+
if (type.startsWith("ACTION_")) return "ACTION";
|
|
162
|
+
if (type.startsWith("APPROVAL_")) return "APPROVAL";
|
|
163
|
+
if (type.includes("RECOVERY") || type === "CHECKPOINT_RECONCILED") return "RECOVERY";
|
|
164
|
+
if (type.startsWith("DIAGNOSTIC") || type.startsWith("DIAGNOSIS")
|
|
165
|
+
|| type === "INTERVENTION_RECORDED" || type === "HYPOTHESIS_DISPOSITION_RECORDED") return "DIAGNOSTIC";
|
|
166
|
+
if (type === "GATE_SATISFIED") return "GATE";
|
|
167
|
+
if (type === "VERIFICATION_RECORDED" || type === "CHECK_RECORDED" || type === "TERMINAL_RESULT_RECORDED") return "CHECK";
|
|
168
|
+
if (type === "COMPLETION_VALIDATED" || type === "COMPLETION_REJECTED") return "COMPLETION";
|
|
169
|
+
if (type === "CONTRACT_VALIDATED" || type === "PLAN_RECORDED" || type === "DECISION_CRITERION_RECORDED") return "CONTRACT";
|
|
170
|
+
if (type === "ROUTE_VALIDATED") return "ROUTING";
|
|
171
|
+
if (type === "TRANSACTION_COMMITTED") return "INTEGRITY";
|
|
172
|
+
return "LIFECYCLE";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function eventLabel(event) {
|
|
176
|
+
const data = event?.data ?? {};
|
|
177
|
+
const title = EVENT_TITLES[event?.type] ?? "Protocol event";
|
|
178
|
+
if (event?.type === "GATE_SATISFIED") {
|
|
179
|
+
const gate = safeIdentifier(data.gate);
|
|
180
|
+
return gate ? `${title}: ${gate}` : title;
|
|
181
|
+
}
|
|
182
|
+
if (["VERIFICATION_RECORDED", "CHECK_RECORDED"].includes(event?.type)) {
|
|
183
|
+
const check = safeIdentifier(data.id ?? data.checkId ?? data.requirement);
|
|
184
|
+
const status = safeStatus(data.status);
|
|
185
|
+
return check ? `${title}: ${check} ${status}` : `${title}: ${status}`;
|
|
186
|
+
}
|
|
187
|
+
if (event?.type?.startsWith("ACTION_") || event?.type?.startsWith("APPROVAL_")) {
|
|
188
|
+
const id = safeIdentifier(data.actionId ?? data.approvalId);
|
|
189
|
+
return id ? `${title}: ${id}` : title;
|
|
190
|
+
}
|
|
191
|
+
return title;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function timelineItem(event) {
|
|
195
|
+
const data = event?.data ?? {};
|
|
196
|
+
const category = categoryForEvent(event);
|
|
197
|
+
const status = typeof data.status === "string" ? safeStatus(data.status) : null;
|
|
198
|
+
const phase = safePhase(event?.phase);
|
|
199
|
+
return {
|
|
200
|
+
id: `event-${event.sequence}`,
|
|
201
|
+
sequence: event.sequence,
|
|
202
|
+
timestamp: typeof event.timestamp === "string" ? event.timestamp : null,
|
|
203
|
+
timestampQuality: event.timestampQuality === "authoritative" ? "authoritative" : "unknown",
|
|
204
|
+
kind: safeIdentifier(event.type) ?? "UNKNOWN_EVENT",
|
|
205
|
+
phase,
|
|
206
|
+
status,
|
|
207
|
+
title: eventLabel(event),
|
|
208
|
+
summary: safeText(event.summary, "Protocol event recorded."),
|
|
209
|
+
category,
|
|
210
|
+
...(event.phaseQuality ? { phaseQuality: event.phaseQuality } : {}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function normalizeOptions(options = {}) {
|
|
215
|
+
const limit = options.limit === undefined ? 100 : options.limit;
|
|
216
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > AUDIT_UX_LIMITS.maxTimelineItems) {
|
|
217
|
+
throw auditUxError("E_AUDIT_UX_INPUT_INVALID", `limit must be an integer between 1 and ${AUDIT_UX_LIMITS.maxTimelineItems}`);
|
|
218
|
+
}
|
|
219
|
+
for (const key of ["beforeSequence", "afterSequence"]) {
|
|
220
|
+
if (options[key] !== undefined && (!Number.isInteger(options[key]) || options[key] < 0)) {
|
|
221
|
+
throw auditUxError("E_AUDIT_UX_INPUT_INVALID", `${key} must be a non-negative integer`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (options.beforeSequence !== undefined && options.afterSequence !== undefined) {
|
|
225
|
+
throw auditUxError("E_AUDIT_UX_INPUT_INVALID", "beforeSequence and afterSequence cannot be combined");
|
|
226
|
+
}
|
|
227
|
+
let categories = options.categories ?? [];
|
|
228
|
+
if (typeof categories === "string") categories = categories.split(",").map((value) => value.trim()).filter(Boolean);
|
|
229
|
+
if (!Array.isArray(categories) || categories.length > AUDIT_UX_CATEGORIES.length) {
|
|
230
|
+
throw auditUxError("E_AUDIT_UX_INPUT_INVALID", "categories must be a bounded array of category names");
|
|
231
|
+
}
|
|
232
|
+
const normalizedCategories = uniqueStrings(categories.map((value) => String(value).toUpperCase()));
|
|
233
|
+
if (normalizedCategories.some((category) => !AUDIT_UX_CATEGORIES.includes(category))) {
|
|
234
|
+
throw auditUxError("E_AUDIT_UX_INPUT_INVALID", "categories contains an unknown audit timeline category");
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
limit,
|
|
238
|
+
beforeSequence: options.beforeSequence,
|
|
239
|
+
afterSequence: options.afterSequence,
|
|
240
|
+
categories: normalizedCategories,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function projectTimeline(events, options) {
|
|
245
|
+
const filtered = events
|
|
246
|
+
.filter((event) => options.beforeSequence === undefined || event.sequence < options.beforeSequence)
|
|
247
|
+
.filter((event) => options.afterSequence === undefined || event.sequence > options.afterSequence)
|
|
248
|
+
.filter((event) => options.categories.length === 0 || options.categories.includes(categoryForEvent(event)))
|
|
249
|
+
.sort((left, right) => left.sequence - right.sequence);
|
|
250
|
+
const hasMore = filtered.length > options.limit;
|
|
251
|
+
const page = hasMore && options.afterSequence === undefined
|
|
252
|
+
? filtered.slice(-options.limit)
|
|
253
|
+
: filtered.slice(0, options.limit);
|
|
254
|
+
const items = page.map(timelineItem);
|
|
255
|
+
return {
|
|
256
|
+
items,
|
|
257
|
+
totalAvailable: filtered.length,
|
|
258
|
+
truncated: hasMore,
|
|
259
|
+
cursor: {
|
|
260
|
+
beforeSequence: options.beforeSequence ?? null,
|
|
261
|
+
afterSequence: options.afterSequence ?? null,
|
|
262
|
+
nextBeforeSequence: hasMore && options.afterSequence === undefined ? items[0]?.sequence ?? null : null,
|
|
263
|
+
nextAfterSequence: hasMore && options.afterSequence !== undefined ? items.at(-1)?.sequence ?? null : null,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function safeCountMap(value) {
|
|
269
|
+
return Object.fromEntries(Object.entries(value ?? {})
|
|
270
|
+
.map(([key, count]) => [safeIdentifier(key) ?? "UNKNOWN", Number.isInteger(count) ? count : 0]));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function projectActions(actions) {
|
|
274
|
+
return {
|
|
275
|
+
total: Number.isInteger(actions.total) ? actions.total : 0,
|
|
276
|
+
byState: safeCountMap(actions.byState),
|
|
277
|
+
byCapability: safeCountMap(actions.byCapability),
|
|
278
|
+
required: Number.isInteger(actions.required) ? actions.required : 0,
|
|
279
|
+
ambiguous: Number.isInteger(actions.ambiguous) ? actions.ambiguous : 0,
|
|
280
|
+
failed: Number.isInteger(actions.failed) ? actions.failed : 0,
|
|
281
|
+
verified: Number.isInteger(actions.verified) ? actions.verified : 0,
|
|
282
|
+
trustedSatisfied: Number.isInteger(actions.trustedSatisfied) ? actions.trustedSatisfied : 0,
|
|
283
|
+
untrustedRequired: Number.isInteger(actions.untrustedRequired) ? actions.untrustedRequired : 0,
|
|
284
|
+
repeatedIdempotencyAttempts: Number.isInteger(actions.repeatedIdempotencyAttempts) ? actions.repeatedIdempotencyAttempts : 0,
|
|
285
|
+
reconciliationCount: Number.isInteger(actions.reconciliationCount) ? actions.reconciliationCount : 0,
|
|
286
|
+
eventCount: Number.isInteger(actions.eventCount) ? actions.eventCount : 0,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function projectChecks(trace) {
|
|
291
|
+
return trace.checks.slice(0, AUDIT_UX_LIMITS.maxChecks).map((check) => ({
|
|
292
|
+
id: safeIdentifier(check.id) ?? "unknown-check",
|
|
293
|
+
requirement: safeText(check.requirement),
|
|
294
|
+
attemptCount: check.attemptCount,
|
|
295
|
+
failedAttempts: check.failedAttempts,
|
|
296
|
+
currentResult: check.currentResult ? safeStatus(check.currentResult) : null,
|
|
297
|
+
attempts: check.attempts.slice(-AUDIT_UX_LIMITS.maxAttemptsPerCheck).map((attempt) => ({
|
|
298
|
+
sequence: Number.isInteger(attempt.sequence) ? attempt.sequence : null,
|
|
299
|
+
timestamp: typeof attempt.at === "string" ? attempt.at : null,
|
|
300
|
+
status: safeStatus(attempt.status),
|
|
301
|
+
exitCode: Number.isInteger(attempt.exitCode) ? attempt.exitCode : null,
|
|
302
|
+
requirement: safeText(attempt.requirement),
|
|
303
|
+
verificationCycle: Number.isInteger(attempt.verificationCycle) ? attempt.verificationCycle : null,
|
|
304
|
+
provenance: safeIdentifier(attempt.provenance),
|
|
305
|
+
executionMode: safeIdentifier(attempt.executionMode),
|
|
306
|
+
})),
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function projectDiagnostics(trace) {
|
|
311
|
+
const cases = trace.diagnostics.cases.slice(0, AUDIT_UX_LIMITS.maxDiagnostics).map((entry) => ({
|
|
312
|
+
sequence: entry.sequence,
|
|
313
|
+
timestamp: typeof entry.at === "string" ? entry.at : null,
|
|
314
|
+
verificationCycle: Number.isInteger(entry.verificationCycle) ? entry.verificationCycle : null,
|
|
315
|
+
diagnosticRevision: Number.isInteger(entry.diagnosticRevision) ? entry.diagnosticRevision : null,
|
|
316
|
+
failureClass: safeIdentifier(entry.failureClass),
|
|
317
|
+
informationGain: safeIdentifier(entry.informationGain),
|
|
318
|
+
}));
|
|
319
|
+
return {
|
|
320
|
+
legacyDiagnosisCount: trace.diagnostics.legacyDiagnoses.length,
|
|
321
|
+
cases,
|
|
322
|
+
caseCount: trace.diagnostics.cases.length,
|
|
323
|
+
interventionCount: trace.diagnostics.interventions.length,
|
|
324
|
+
dispositionCount: trace.diagnostics.dispositions.length,
|
|
325
|
+
invalidRevisionCount: trace.diagnostics.invalidRevisions.length,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function projectApprovals(approvals) {
|
|
330
|
+
return approvals.slice(0, AUDIT_UX_LIMITS.maxApprovals).map((approval) => ({
|
|
331
|
+
approvalId: safeIdentifier(approval.approvalId) ?? "unknown-approval",
|
|
332
|
+
actionId: safeIdentifier(approval.actionId),
|
|
333
|
+
status: safeStatus(approval.status),
|
|
334
|
+
decision: safeIdentifier(approval.decision),
|
|
335
|
+
requestedAt: typeof approval.requestedAt === "string" ? approval.requestedAt : null,
|
|
336
|
+
resolvedAt: typeof approval.resolvedAt === "string" ? approval.resolvedAt : null,
|
|
337
|
+
authorityKind: safeIdentifier(approval.authorityKind),
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function projectCompletion(audit, ownership) {
|
|
342
|
+
const completion = audit.completion ?? {};
|
|
343
|
+
const coverage = Array.isArray(completion.coverage) ? completion.coverage : [];
|
|
344
|
+
const requirementsSatisfied = coverage.length > 0 && coverage.every((item) => item.status === "COVERED");
|
|
345
|
+
const valid = completion.status === "VALID";
|
|
346
|
+
return {
|
|
347
|
+
state: valid ? "COMPLETE" : "INCOMPLETE",
|
|
348
|
+
valid,
|
|
349
|
+
requirementsSatisfied,
|
|
350
|
+
claimsReleased: ownership.claimState === "RELEASED_BY_COMPLETION",
|
|
351
|
+
publicationStatus: safeIdentifier(audit.publicationStatus),
|
|
352
|
+
productionReadiness: safeIdentifier(audit.productionReadiness),
|
|
353
|
+
reasonCodes: uniqueStrings(projectErrors(audit.errors).map((error) => error.code)),
|
|
354
|
+
coverage: coverage.slice(0, AUDIT_UX_LIMITS.maxChecks).map((item) => ({
|
|
355
|
+
requirement: safeText(item.requirement),
|
|
356
|
+
status: safeStatus(item.status),
|
|
357
|
+
})),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function projectReasonCodes(...collections) {
|
|
362
|
+
return uniqueStrings(collections.flat().map((value) => {
|
|
363
|
+
if (typeof value === "string") return value;
|
|
364
|
+
return value?.code;
|
|
365
|
+
}).map((value) => safeIdentifier(value)).filter(Boolean));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function buildProjection({ status, audit, report, history, trace, ownership, next, approvals, timeline }) {
|
|
369
|
+
const integrityErrors = [
|
|
370
|
+
...projectErrors(audit.errors),
|
|
371
|
+
...projectErrors(trace.integrity.errors),
|
|
372
|
+
...projectErrors(history.integrity.errors),
|
|
373
|
+
...projectErrors(ownership.ownershipErrors),
|
|
374
|
+
];
|
|
375
|
+
const checks = projectChecks(trace);
|
|
376
|
+
const completion = projectCompletion(audit, ownership);
|
|
377
|
+
const reasonCodes = projectReasonCodes(
|
|
378
|
+
status.reasonCodes,
|
|
379
|
+
audit.errors,
|
|
380
|
+
trace.integrity.errors,
|
|
381
|
+
ownership.reasonCodes,
|
|
382
|
+
next.reasonCodes,
|
|
383
|
+
);
|
|
384
|
+
const auditStatus = safeIdentifier(audit.status) ?? "INVALID";
|
|
385
|
+
const reportVerdict = safeIdentifier(report.verdict);
|
|
386
|
+
const traceValid = trace.integrity.valid === true;
|
|
387
|
+
const historyValid = history.integrity.valid === true;
|
|
388
|
+
return {
|
|
389
|
+
schemaVersion: AUDIT_UX_SCHEMA_VERSION,
|
|
390
|
+
protocolVersion: trace.protocolVersion,
|
|
391
|
+
taskId: trace.task.id,
|
|
392
|
+
readOnly: true,
|
|
393
|
+
authority: {
|
|
394
|
+
readOnly: true,
|
|
395
|
+
lifecycleAuthority: false,
|
|
396
|
+
evidenceAuthority: false,
|
|
397
|
+
completionAuthority: false,
|
|
398
|
+
mutationAuthority: false,
|
|
399
|
+
externalExecution: false,
|
|
400
|
+
},
|
|
401
|
+
health: {
|
|
402
|
+
phase: safePhase(status.phase ?? trace.task.phase),
|
|
403
|
+
protocolValid: status.protocol?.status === "VALID",
|
|
404
|
+
auditStatus,
|
|
405
|
+
reportVerdict,
|
|
406
|
+
completionStatus: completion.valid ? "VALID" : "INCOMPLETE",
|
|
407
|
+
ownershipValid: ownership.ownershipValid === true,
|
|
408
|
+
claimState: safeIdentifier(ownership.claimState),
|
|
409
|
+
mutationAllowed: ownership.mutationAllowed === true,
|
|
410
|
+
recoveryStatus: safeIdentifier(ownership.recoveryStatus),
|
|
411
|
+
currentNextAction: safeIdentifier(next.nextAction),
|
|
412
|
+
historyQuality: safeIdentifier(history.historyQuality.level),
|
|
413
|
+
traceIntegrity: traceValid,
|
|
414
|
+
reasonCodes,
|
|
415
|
+
},
|
|
416
|
+
lifecycle: {
|
|
417
|
+
phase: safePhase(trace.task.phase),
|
|
418
|
+
status: safeIdentifier(trace.task.status),
|
|
419
|
+
revision: Number.isInteger(trace.task.revision) ? trace.task.revision : null,
|
|
420
|
+
verificationCycle: Number.isInteger(trace.task.verificationCycle) ? trace.task.verificationCycle : null,
|
|
421
|
+
nextAction: safeIdentifier(next.nextAction),
|
|
422
|
+
terminal: next.terminal === true,
|
|
423
|
+
},
|
|
424
|
+
timeline,
|
|
425
|
+
verification: {
|
|
426
|
+
checks,
|
|
427
|
+
checkCount: trace.checks.length,
|
|
428
|
+
totalAttempts: trace.checks.reduce((total, check) => total + check.attemptCount, 0),
|
|
429
|
+
failedAttempts: trace.checks.reduce((total, check) => total + check.failedAttempts, 0),
|
|
430
|
+
coverage: completion.coverage,
|
|
431
|
+
},
|
|
432
|
+
ownership: {
|
|
433
|
+
claimState: safeIdentifier(ownership.claimState),
|
|
434
|
+
ownershipValid: ownership.ownershipValid === true,
|
|
435
|
+
mutationAllowed: ownership.mutationAllowed === true,
|
|
436
|
+
recoveryStatus: safeIdentifier(ownership.recoveryStatus),
|
|
437
|
+
historicalWriteClaims: ownership.historicalWriteClaims.slice(0, AUDIT_UX_LIMITS.maxActions).map(safeText).filter(Boolean),
|
|
438
|
+
effectiveWriteClaims: ownership.effectiveWriteClaims.slice(0, AUDIT_UX_LIMITS.maxActions).map(safeText).filter(Boolean),
|
|
439
|
+
reasonCodes: projectReasonCodes(ownership.reasonCodes, ownership.ownershipErrors),
|
|
440
|
+
},
|
|
441
|
+
diagnostics: projectDiagnostics(trace),
|
|
442
|
+
completion,
|
|
443
|
+
integrity: {
|
|
444
|
+
valid: auditStatus === "VALID" && traceValid && historyValid,
|
|
445
|
+
auditStatus,
|
|
446
|
+
traceValid,
|
|
447
|
+
historyValid,
|
|
448
|
+
reasonCodes: projectReasonCodes(integrityErrors),
|
|
449
|
+
},
|
|
450
|
+
history: {
|
|
451
|
+
quality: safeIdentifier(history.historyQuality.level),
|
|
452
|
+
totalEventCount: history.summary.totalEventCount,
|
|
453
|
+
returnedEventCount: timeline.items.length,
|
|
454
|
+
truncated: timeline.truncated,
|
|
455
|
+
},
|
|
456
|
+
actions: {
|
|
457
|
+
...projectActions(trace.actions),
|
|
458
|
+
bounded: trace.actions.total > AUDIT_UX_LIMITS.maxActions,
|
|
459
|
+
},
|
|
460
|
+
approvals: projectApprovals(approvals),
|
|
461
|
+
recovery: {
|
|
462
|
+
active: ownership.recoveryStatus === "RELEASED_BY_RECOVERY",
|
|
463
|
+
status: safeIdentifier(ownership.recoveryStatus),
|
|
464
|
+
events: trace.recovery.slice(0, AUDIT_UX_LIMITS.maxDiagnostics).map((event) => ({
|
|
465
|
+
sequence: event.sequence,
|
|
466
|
+
timestamp: typeof event.at === "string" ? event.at : null,
|
|
467
|
+
kind: safeIdentifier(event.type),
|
|
468
|
+
})),
|
|
469
|
+
},
|
|
470
|
+
links: {
|
|
471
|
+
status: "task/status",
|
|
472
|
+
ownership: "task/ownership",
|
|
473
|
+
contract: "task/contract",
|
|
474
|
+
auditView: "task/audit-view",
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export async function buildAuditUxView({
|
|
480
|
+
target,
|
|
481
|
+
packageRoot,
|
|
482
|
+
taskId,
|
|
483
|
+
limit,
|
|
484
|
+
beforeSequence,
|
|
485
|
+
afterSequence,
|
|
486
|
+
categories,
|
|
487
|
+
authorityContext,
|
|
488
|
+
runtimeContext,
|
|
489
|
+
} = {}) {
|
|
490
|
+
if (typeof taskId !== "string" || !taskId.trim()) {
|
|
491
|
+
throw auditUxError("E_TASK_REQUIRED", "Resource task/audit-view requires a taskId");
|
|
492
|
+
}
|
|
493
|
+
const options = normalizeOptions({ limit, beforeSequence, afterSequence, categories });
|
|
494
|
+
const task = await findTaskById(target, taskId, packageRoot);
|
|
495
|
+
if (!task) throw auditUxError("E_TASK_NOT_FOUND", `Task ${taskId} does not exist`);
|
|
496
|
+
|
|
497
|
+
const [status, audit, report, history, trace, ownership, next, approvals] = await Promise.all([
|
|
498
|
+
runStatus({ target, packageRoot, taskId }),
|
|
499
|
+
evaluateAudit({ target, packageRoot, taskId, authorityContext, runtimeContext }),
|
|
500
|
+
runReport({ target, packageRoot, taskId }),
|
|
501
|
+
buildTaskHistory({ target, packageRoot, taskId }),
|
|
502
|
+
buildTaskTrace({ target, packageRoot, taskId }),
|
|
503
|
+
resolveTaskClaimState(target, { packageRoot, taskId }),
|
|
504
|
+
runNext({ target, packageRoot, taskId, authorityContext, runtimeContext, compact: false }),
|
|
505
|
+
listApprovals(target, { packageRoot, taskId }),
|
|
506
|
+
]);
|
|
507
|
+
const timeline = projectTimeline(trace.events, options);
|
|
508
|
+
const projection = buildProjection({ status, audit, report, history, trace, ownership, next, approvals, timeline });
|
|
509
|
+
const serializedBytes = Buffer.byteLength(JSON.stringify(projection), "utf8");
|
|
510
|
+
if (serializedBytes > AUDIT_UX_LIMITS.maxOutputBytes || serializedBytes > INTEGRATION_LIMITS.maxOutputBytes) {
|
|
511
|
+
throw auditUxError("E_AUDIT_UX_OUTPUT_LIMIT", "Audit UX projection exceeds its bounded output limit");
|
|
512
|
+
}
|
|
513
|
+
return projection;
|
|
514
|
+
}
|
package/src/core/audit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
|
|
1
2
|
import { ARTIFACT_PATHS } from "./artifacts.js";
|
|
2
3
|
import { evaluateCompletion } from "./completion.js";
|
|
3
4
|
import { readManifest } from "./manifest.js";
|
|
@@ -108,7 +109,11 @@ async function evaluateLocalAttestation({ target, packageRoot, taskId, completio
|
|
|
108
109
|
return { ...base, status: "VALID" };
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
export async function evaluateAudit({
|
|
112
|
+
export async function evaluateAudit(options = {}) {
|
|
113
|
+
return withProjectReadSnapshot(options.target, () => evaluateSelectedAudit(options));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function evaluateSelectedAudit({
|
|
112
117
|
target,
|
|
113
118
|
packageRoot,
|
|
114
119
|
strict = false,
|