forgeos 0.1.0-alpha.7 → 0.1.0-alpha.71
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/.agents/skills/forge-agent-fabric/SKILL.md +178 -0
- package/.agents/skills/forge-agent-fabric/references/managed-workflows.md +95 -0
- package/.agents/skills/forge-agent-fabric/references/repository-analysis.md +102 -0
- package/.agents/skills/forge-agent-fabric/scripts/fabric.mjs +46 -0
- package/.agents/skills/forge-agent-fabric/scripts/repository.mjs +44 -0
- package/.npmignore +10 -0
- package/AGENTS.md +142 -90
- package/CHANGELOG.md +870 -9
- package/LICENSE +21 -0
- package/README.md +121 -6
- package/adapters/go/README.md +23 -0
- package/adapters/go/go.mod +3 -0
- package/adapters/go/http.go +149 -0
- package/adapters/go/registry.go +234 -0
- package/adapters/go/types.go +136 -0
- package/adapters/java/README.md +68 -0
- package/adapters/java/pom.xml +47 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
- package/adapters/java-spring-boot-starter/README.md +32 -0
- package/adapters/java-spring-boot-starter/pom.xml +49 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
- package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
- package/bin/forge.mjs +18 -0
- package/docs/agent-fabric-programs.md +151 -0
- package/docs/agent-fabric.md +582 -0
- package/docs/architecture/agent-fabric/PORTABLE_PROJECTS_PLAN.md +136 -0
- package/docs/architecture/agent-fabric/README.md +180 -0
- package/docs/architecture/agent-fabric/REPOSITORY_MANIFEST_PLAN.md +284 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_ADR.md +81 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS.md +39 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS_EVIDENCE.md +53 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_EVIDENCE.md +49 -0
- package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_PLAN.md +590 -0
- package/docs/cair-protocol.md +103 -0
- package/docs/changelog.md +1070 -0
- package/docs/forge-protocol.md +189 -0
- package/docs/repository-agent-benchmark.md +179 -0
- package/docs/repository-analysis.md +139 -0
- package/docs/repository-context-quality.md +179 -0
- package/docs/repository-runtime-observation.md +82 -0
- package/examples/agent-fabric-v2/README.md +70 -0
- package/examples/agent-fabric-v2/item-child.workflow.ts +11 -0
- package/examples/agent-fabric-v2/make-request.mjs +4 -0
- package/examples/agent-fabric-v2/map-child.workflow.ts +10 -0
- package/examples/agent-fabric-v2/migrate.workflow.ts +35 -0
- package/examples/agent-fabric-v2/registry.example.json +256 -0
- package/examples/agent-fabric-v2/run-ui-fixture.ts +48 -0
- package/examples/agent-fabric-v2/ui-audit.workflow.ts +28 -0
- package/examples/agent-fabric-v2/ui-fixture-worker.cjs +36 -0
- package/examples/agent-fabric-v2/visual-population.json +13 -0
- package/examples/agent-fabric-v2/workers/check.mjs +2 -0
- package/examples/agent-fabric-v2/workers/common.mjs +7 -0
- package/examples/agent-fabric-v2/workers/discover.mjs +2 -0
- package/examples/agent-fabric-v2/workers/migrate.mjs +4 -0
- package/examples/agent-fabric-v2/workers/review.mjs +3 -0
- package/examples/go-billing/go.mod +7 -0
- package/examples/go-billing/main.go +120 -0
- package/examples/java-billing/pom.xml +60 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
- package/package.json +60 -5
- package/schemas/agent-fabric/v0.1/control-event.schema.json +281 -0
- package/schemas/forge-manifest.schema.json +389 -0
- package/scripts/field-test-forgeos.mjs +1021 -0
- package/scripts/install-agent-fabric-skill.mjs +105 -0
- package/src/forge/_generated/releaseManifest.json +1 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +1592 -125
- package/src/forge/agent-adapters/types.ts +225 -1
- package/src/forge/agent-fabric/adapter.ts +131 -0
- package/src/forge/agent-fabric/attached-snapshot.ts +55 -0
- package/src/forge/agent-fabric/attached-task-contract.ts +105 -0
- package/src/forge/agent-fabric/attached-task-service.ts +127 -0
- package/src/forge/agent-fabric/attached-task-store.ts +109 -0
- package/src/forge/agent-fabric/authority.ts +298 -0
- package/src/forge/agent-fabric/canonical.ts +135 -0
- package/src/forge/agent-fabric/codex-adversarial-review.ts +229 -0
- package/src/forge/agent-fabric/codex-sdk-worker.ts +203 -0
- package/src/forge/agent-fabric/conductor.ts +754 -0
- package/src/forge/agent-fabric/dictionary.ts +12 -0
- package/src/forge/agent-fabric/errors.ts +32 -0
- package/src/forge/agent-fabric/hardened-conductor.ts +485 -0
- package/src/forge/agent-fabric/hardened-reducer.ts +211 -0
- package/src/forge/agent-fabric/index.ts +48 -0
- package/src/forge/agent-fabric/journal.ts +116 -0
- package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -0
- package/src/forge/agent-fabric/local-adaptive-harness.ts +343 -0
- package/src/forge/agent-fabric/local-adaptive-service.ts +398 -0
- package/src/forge/agent-fabric/local-adaptive-worker.ts +146 -0
- package/src/forge/agent-fabric/local-approval-window.ts +211 -0
- package/src/forge/agent-fabric/local-change-review-service.ts +374 -0
- package/src/forge/agent-fabric/local-coding-worker.ts +285 -0
- package/src/forge/agent-fabric/local-control-store.ts +239 -0
- package/src/forge/agent-fabric/local-effect-broker.ts +348 -0
- package/src/forge/agent-fabric/local-evolution-approval.ts +81 -0
- package/src/forge/agent-fabric/local-evolution-profile.ts +87 -0
- package/src/forge/agent-fabric/local-evolution-registry.ts +427 -0
- package/src/forge/agent-fabric/local-evolution-service.ts +200 -0
- package/src/forge/agent-fabric/local-intelligence.ts +281 -0
- package/src/forge/agent-fabric/local-paths.ts +18 -0
- package/src/forge/agent-fabric/local-task-contract.ts +203 -0
- package/src/forge/agent-fabric/local-task-inbox.ts +543 -0
- package/src/forge/agent-fabric/local-task-server.ts +374 -0
- package/src/forge/agent-fabric/local-task-service.ts +982 -0
- package/src/forge/agent-fabric/local-verification.ts +551 -0
- package/src/forge/agent-fabric/managed-environment.ts +205 -0
- package/src/forge/agent-fabric/managed-run-contract.ts +103 -0
- package/src/forge/agent-fabric/managed-run-service.ts +401 -0
- package/src/forge/agent-fabric/managed-run-store.ts +165 -0
- package/src/forge/agent-fabric/managed-workspace.ts +253 -0
- package/src/forge/agent-fabric/p0a.ts +104 -0
- package/src/forge/agent-fabric/p0b-model-adapter.ts +434 -0
- package/src/forge/agent-fabric/planning.ts +155 -0
- package/src/forge/agent-fabric/program-api.ts +37 -0
- package/src/forge/agent-fabric/program-contract.ts +283 -0
- package/src/forge/agent-fabric/program-dsl.ts +126 -0
- package/src/forge/agent-fabric/program-evidence.ts +22 -0
- package/src/forge/agent-fabric/program-image.ts +15 -0
- package/src/forge/agent-fabric/program-lock.ts +50 -0
- package/src/forge/agent-fabric/program-owner.ts +16 -0
- package/src/forge/agent-fabric/program-scheduler.ts +38 -0
- package/src/forge/agent-fabric/program-service.ts +793 -0
- package/src/forge/agent-fabric/program-store.ts +142 -0
- package/src/forge/agent-fabric/program-structure.ts +65 -0
- package/src/forge/agent-fabric/program-types.ts +110 -0
- package/src/forge/agent-fabric/program-view.ts +12 -0
- package/src/forge/agent-fabric/program-worker.ts +108 -0
- package/src/forge/agent-fabric/project-profile.ts +50 -0
- package/src/forge/agent-fabric/project-registry.ts +120 -0
- package/src/forge/agent-fabric/project-runtime.ts +85 -0
- package/src/forge/agent-fabric/readiness.ts +26 -0
- package/src/forge/agent-fabric/reducer.ts +863 -0
- package/src/forge/agent-fabric/repository-context.ts +83 -0
- package/src/forge/agent-fabric/resource-ledger.ts +266 -0
- package/src/forge/agent-fabric/serialized-local-adapter.ts +60 -0
- package/src/forge/agent-fabric/types.ts +424 -0
- package/src/forge/agent-fabric/validation.ts +500 -0
- package/src/forge/agent-fabric/workflow-engine.ts +218 -0
- package/src/forge/agent-fabric/workflow-task-actions.ts +104 -0
- package/src/forge/agent-fabric/workflow-templates.ts +11 -0
- package/src/forge/agent-memory/bridge.ts +1728 -0
- package/src/forge/agent-memory/context-pack.ts +284 -0
- package/src/forge/agent-memory/hook-runner.ts +331 -0
- package/src/forge/agent-memory/mcp.ts +597 -0
- package/src/forge/agent-memory/normalize.ts +498 -0
- package/src/forge/agent-memory/redaction.ts +103 -0
- package/src/forge/agent-memory/sources/claude-code.ts +51 -0
- package/src/forge/agent-memory/sources/codex-hook-runner.mjs +343 -0
- package/src/forge/agent-memory/sources/codex.ts +161 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +215 -0
- package/src/forge/bench.ts +248 -0
- package/src/forge/brownfield-import/index.ts +896 -0
- package/src/forge/brownfield-import/types.ts +127 -0
- package/src/forge/cair/action-journal.ts +61 -0
- package/src/forge/cair/action-parser.ts +314 -0
- package/src/forge/cair/action-validator.ts +40 -0
- package/src/forge/cair/actions.ts +1818 -0
- package/src/forge/cair/format.ts +77 -0
- package/src/forge/cair/index.ts +118 -0
- package/src/forge/cair/query.ts +478 -0
- package/src/forge/cair/snapshot.ts +315 -0
- package/src/forge/cair/types.ts +255 -0
- package/src/forge/cli/adaptive.ts +51 -0
- package/src/forge/cli/ai.ts +16 -1
- package/src/forge/cli/auth.ts +459 -15
- package/src/forge/cli/authmd.ts +441 -0
- package/src/forge/cli/baseline.ts +112 -0
- package/src/forge/cli/build.ts +20 -4
- package/src/forge/cli/changed.ts +418 -0
- package/src/forge/cli/codex-app-server.ts +877 -0
- package/src/forge/cli/commands.ts +1859 -20
- package/src/forge/cli/db.ts +209 -2
- package/src/forge/cli/deploy.ts +1339 -0
- package/src/forge/cli/deps.ts +150 -23
- package/src/forge/cli/dev.ts +1137 -53
- package/src/forge/cli/docs.ts +265 -0
- package/src/forge/cli/doctor.ts +297 -0
- package/src/forge/cli/env-placeholders.ts +62 -0
- package/src/forge/cli/evolution.ts +40 -0
- package/src/forge/cli/fabric.ts +253 -0
- package/src/forge/cli/field-test.ts +568 -0
- package/src/forge/cli/golden-path.ts +487 -0
- package/src/forge/cli/handoff.ts +336 -0
- package/src/forge/cli/index.ts +1 -0
- package/src/forge/cli/last-run.ts +84 -0
- package/src/forge/cli/main.ts +196 -3
- package/src/forge/cli/new.ts +169 -17
- package/src/forge/cli/next-actions.ts +23 -0
- package/src/forge/cli/output.ts +398 -6
- package/src/forge/cli/parse.ts +1377 -38
- package/src/forge/cli/progress.ts +51 -0
- package/src/forge/cli/query.ts +32 -0
- package/src/forge/cli/release.ts +35 -11
- package/src/forge/cli/repository.ts +151 -0
- package/src/forge/cli/run.ts +42 -0
- package/src/forge/cli/secrets.ts +137 -1
- package/src/forge/cli/seed.ts +581 -0
- package/src/forge/cli/self-host.ts +56 -14
- package/src/forge/cli/studio.ts +2282 -0
- package/src/forge/cli/verify.ts +1458 -32
- package/src/forge/cli/windows.ts +23 -0
- package/src/forge/cli/workos.ts +3509 -0
- package/src/forge/compiler/agent-contract/build.ts +728 -64
- package/src/forge/compiler/agent-contract/types.ts +45 -2
- package/src/forge/compiler/ai-registry/parse.ts +71 -2
- package/src/forge/compiler/api-surface/build.ts +47 -0
- package/src/forge/compiler/app-graph/build.ts +112 -9
- package/src/forge/compiler/app-graph/extract.ts +107 -0
- package/src/forge/compiler/app-graph/module-graph.ts +73 -78
- package/src/forge/compiler/app-graph/parser.ts +24 -24
- package/src/forge/compiler/app-graph/profile.ts +26 -0
- package/src/forge/compiler/app-graph/tsconfig-hash.ts +41 -4
- package/src/forge/compiler/app-graph/versions.ts +1 -1
- package/src/forge/compiler/classifier/capabilities.ts +3 -2
- package/src/forge/compiler/classifier/classify.ts +32 -8
- package/src/forge/compiler/classifier/secrets.ts +3 -2
- package/src/forge/compiler/classifier/signals.ts +91 -1
- package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
- package/src/forge/compiler/client-sdk/render-client.ts +295 -13
- package/src/forge/compiler/data-graph/build.ts +29 -1
- package/src/forge/compiler/data-graph/parse.ts +20 -4
- package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
- package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
- package/src/forge/compiler/data-graph/sql/types.ts +1 -0
- package/src/forge/compiler/diagnostics/codes.ts +38 -0
- package/src/forge/compiler/diagnostics/create.ts +28 -2
- package/src/forge/compiler/diagnostics/index.ts +2 -0
- package/src/forge/compiler/emitter/barrel.ts +3 -0
- package/src/forge/compiler/emitter/render.ts +9 -0
- package/src/forge/compiler/external-manifest/registry.ts +217 -0
- package/src/forge/compiler/external-manifest/types.ts +91 -0
- package/src/forge/compiler/external-manifest/validate.ts +373 -0
- package/src/forge/compiler/frontend-graph/build.ts +142 -14
- package/src/forge/compiler/integration/add.ts +991 -33
- package/src/forge/compiler/integration/plan.ts +43 -12
- package/src/forge/compiler/integration/render.ts +29 -0
- package/src/forge/compiler/integration/snapshot.ts +2 -0
- package/src/forge/compiler/integration/templates/convex.ts +70 -0
- package/src/forge/compiler/integration/templates/index.ts +1 -0
- package/src/forge/compiler/integration/templates/render.ts +38 -0
- package/src/forge/compiler/integration/templates/types.ts +3 -0
- package/src/forge/compiler/integration/templates/workos.ts +1439 -0
- package/src/forge/compiler/make-registry/build.ts +8 -9
- package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
- package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
- package/src/forge/compiler/orchestrator/manifest.ts +1 -1
- package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
- package/src/forge/compiler/orchestrator/plan.ts +249 -10
- package/src/forge/compiler/orchestrator/profile.ts +65 -0
- package/src/forge/compiler/orchestrator/run.ts +101 -33
- package/src/forge/compiler/orchestrator/serialize.ts +33 -8
- package/src/forge/compiler/orchestrator/types.ts +1 -1
- package/src/forge/compiler/package-graph/compiler.ts +3 -1
- package/src/forge/compiler/package-graph/constants.ts +1 -1
- package/src/forge/compiler/package-graph/dts-extractor.ts +14 -1
- package/src/forge/compiler/package-manager/adapter.ts +4 -1
- package/src/forge/compiler/package-manager/commands.ts +4 -0
- package/src/forge/compiler/package-manager/executor.ts +30 -1
- package/src/forge/compiler/package-manager/version.ts +36 -12
- package/src/forge/compiler/package-upgrades/planner.ts +155 -18
- package/src/forge/compiler/package-upgrades/types.ts +2 -0
- package/src/forge/compiler/policy-registry/build.ts +47 -2
- package/src/forge/compiler/policy-registry/parse.ts +32 -2
- package/src/forge/compiler/recipes/definitions.ts +63 -0
- package/src/forge/compiler/recipes/index.ts +2 -0
- package/src/forge/compiler/recipes/profiles.ts +81 -0
- package/src/forge/compiler/recipes/registry.ts +6 -0
- package/src/forge/compiler/test-graph/build.ts +11 -3
- package/src/forge/compiler/types/app-graph.ts +8 -2
- package/src/forge/compiler/types/cli.ts +76 -1
- package/src/forge/compiler/types/dev-manifest.ts +4 -0
- package/src/forge/compiler/types/emit.ts +2 -0
- package/src/forge/compiler/types/frontend-graph.ts +2 -2
- package/src/forge/compiler/types/integration.ts +1 -0
- package/src/forge/compiler/types/package-graph.ts +6 -0
- package/src/forge/compiler/types/policy-registry.ts +3 -1
- package/src/forge/delta/broker-runner.mjs +5 -0
- package/src/forge/delta/broker.ts +429 -0
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +238 -0
- package/src/forge/delta/git-observer.ts +67 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +25 -0
- package/src/forge/delta/process-identity.ts +37 -0
- package/src/forge/delta/queue-status.ts +190 -0
- package/src/forge/delta/recorder.ts +540 -0
- package/src/forge/delta/redaction.ts +50 -0
- package/src/forge/delta/schema.ts +240 -0
- package/src/forge/delta/session.ts +142 -0
- package/src/forge/delta/status.ts +1139 -0
- package/src/forge/delta/store.ts +3329 -0
- package/src/forge/delta/timeline.ts +178 -0
- package/src/forge/dev/server.ts +1061 -22
- package/src/forge/dev/types.ts +15 -1
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/dev-console/cycle.ts +285 -33
- package/src/forge/dev-console/types.ts +47 -1
- package/src/forge/impact/index.ts +222 -30
- package/src/forge/impact/types.ts +33 -1
- package/src/forge/intent/index.ts +35 -16
- package/src/forge/make/fields.ts +26 -0
- package/src/forge/make/index.ts +42 -6
- package/src/forge/make/templates.ts +211 -4
- package/src/forge/make/types.ts +2 -1
- package/src/forge/policy.ts +1 -1
- package/src/forge/react/index.ts +160 -2
- package/src/forge/refactor/index.ts +1 -0
- package/src/forge/repair/rules/index.ts +2 -2
- package/src/forge/repository-analysis/adapters/common.ts +72 -0
- package/src/forge/repository-analysis/adapters/docker.ts +185 -0
- package/src/forge/repository-analysis/adapters/java.ts +133 -0
- package/src/forge/repository-analysis/adapters/typescript.ts +350 -0
- package/src/forge/repository-analysis/adapters/vue.ts +71 -0
- package/src/forge/repository-analysis/analyze.ts +434 -0
- package/src/forge/repository-analysis/benchmark.ts +336 -0
- package/src/forge/repository-analysis/cache-gc.ts +154 -0
- package/src/forge/repository-analysis/check-selection.ts +123 -0
- package/src/forge/repository-analysis/context-node.ts +17 -0
- package/src/forge/repository-analysis/context.ts +456 -0
- package/src/forge/repository-analysis/http-resolution.ts +135 -0
- package/src/forge/repository-analysis/identity.ts +45 -0
- package/src/forge/repository-analysis/index.ts +18 -0
- package/src/forge/repository-analysis/nuxt-resolution.ts +188 -0
- package/src/forge/repository-analysis/quality.ts +60 -0
- package/src/forge/repository-analysis/retrieval.ts +309 -0
- package/src/forge/repository-analysis/runtime-artifacts.ts +195 -0
- package/src/forge/repository-analysis/runtime-observation.ts +325 -0
- package/src/forge/repository-analysis/scanner.ts +174 -0
- package/src/forge/repository-analysis/storage.ts +193 -0
- package/src/forge/repository-analysis/test-associations.ts +59 -0
- package/src/forge/repository-analysis/types.ts +99 -0
- package/src/forge/repository-manifest/index.ts +178 -0
- package/src/forge/repository-manifest/types.ts +63 -0
- package/src/forge/review/index.ts +162 -12
- package/src/forge/review/types.ts +15 -0
- package/src/forge/runtime/ai/providers.ts +19 -4
- package/src/forge/runtime/auth/config.ts +17 -0
- package/src/forge/runtime/auth/evaluate.ts +15 -2
- package/src/forge/runtime/auth/resolve.ts +61 -6
- package/src/forge/runtime/auth/types.ts +1 -0
- package/src/forge/runtime/db/factory.ts +1 -3
- package/src/forge/runtime/db/generated-client.ts +13 -2
- package/src/forge/runtime/db/memory-adapter.ts +139 -32
- package/src/forge/runtime/db/pglite-adapter.ts +285 -2
- package/src/forge/runtime/executor.ts +129 -16
- package/src/forge/runtime/external/bridge.ts +663 -0
- package/src/forge/runtime/runner/run-entry.ts +16 -7
- package/src/forge/runtime/telemetry/buffer.ts +4 -1
- package/src/forge/runtime/telemetry/scrubber.ts +35 -5
- package/src/forge/runtime/webhooks/security.ts +12 -7
- package/src/forge/server.ts +71 -7
- package/src/forge/ui/index.ts +831 -19
- package/src/forge/ui/types.ts +3 -1
- package/src/forge/version.ts +1 -1
- package/src/forge/vue/index.ts +422 -0
- package/src/forge/workspace/baseline.ts +112 -0
- package/src/forge/workspace/change-summary.ts +292 -0
- package/src/forge/workspace/forge-cli.ts +42 -0
- package/src/forge/workspace/git-summary.ts +393 -0
- package/templates/agent-workroom/AGENTS.md +29 -0
- package/templates/agent-workroom/README.md +34 -0
- package/templates/agent-workroom/forge.config.ts +3 -0
- package/templates/agent-workroom/package.json +34 -0
- package/templates/agent-workroom/pnpm-workspace.yaml +2 -0
- package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
- package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
- package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
- package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
- package/templates/agent-workroom/src/forge/schema.ts +54 -0
- package/templates/agent-workroom/src/policies.ts +6 -0
- package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
- package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
- package/templates/agent-workroom/tsconfig.json +16 -0
- package/templates/agent-workroom/web/index.html +13 -0
- package/templates/agent-workroom/web/package.json +21 -0
- package/templates/agent-workroom/web/src/App.tsx +345 -0
- package/templates/agent-workroom/web/src/lib/forge.ts +20 -0
- package/templates/agent-workroom/web/src/main.tsx +13 -0
- package/templates/agent-workroom/web/src/styles.css +545 -0
- package/templates/agent-workroom/web/tsconfig.json +27 -0
- package/templates/agent-workroom/web/vite.config.ts +36 -0
- package/templates/b2b-support-web/package.json +2 -0
- package/templates/b2b-support-web/pnpm-workspace.yaml +2 -0
- package/templates/b2b-support-web/web/app/page.tsx +2 -2
- package/templates/b2b-support-web/web/lib/forge.ts +9 -2
- package/templates/b2b-support-web/web/next.config.ts +24 -0
- package/templates/b2b-support-web/web/package.json +1 -1
- package/templates/minimal-web/package.json +2 -1
- package/templates/minimal-web/pnpm-workspace.yaml +2 -0
- package/templates/minimal-web/web/index.html +1 -0
- package/templates/minimal-web/web/src/App.tsx +12 -6
- package/templates/minimal-web/web/src/lib/forge.ts +9 -2
- package/templates/minimal-web/web/src/styles.css +34 -0
- package/templates/minimal-web/web/vite.config.ts +36 -0
- package/templates/nuxt-web/.vscode/settings.json +14 -0
- package/templates/nuxt-web/.yarnrc.yml +1 -0
- package/templates/nuxt-web/README.md +30 -0
- package/templates/nuxt-web/forge.config.ts +3 -0
- package/templates/nuxt-web/package.json +37 -0
- package/templates/nuxt-web/pnpm-workspace.yaml +2 -0
- package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
- package/templates/nuxt-web/src/commands/createNote.ts +26 -0
- package/templates/nuxt-web/src/forge/schema.ts +12 -0
- package/templates/nuxt-web/src/policies.ts +6 -0
- package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
- package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
- package/templates/nuxt-web/tsconfig.json +17 -0
- package/templates/nuxt-web/web/app.vue +67 -0
- package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
- package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
- package/templates/nuxt-web/web/composables/forge.ts +13 -0
- package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
- package/templates/nuxt-web/web/nuxt.config.ts +11 -0
- package/templates/nuxt-web/web/package.json +22 -0
- package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
- package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
- package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
- package/templates/nuxt-web/web/tsconfig.json +6 -0
- package/templates/vendor-access/.vscode/settings.json +14 -0
- package/templates/vendor-access/README.md +30 -0
- package/templates/vendor-access/forge.config.ts +3 -0
- package/templates/vendor-access/package.json +35 -0
- package/templates/vendor-access/pnpm-workspace.yaml +2 -0
- package/templates/vendor-access/src/commands/addEvidence.ts +42 -0
- package/templates/vendor-access/src/commands/approveAccessRequest.ts +43 -0
- package/templates/vendor-access/src/commands/createAccessRequest.ts +49 -0
- package/templates/vendor-access/src/commands/seedVendorAccessDemo.ts +200 -0
- package/templates/vendor-access/src/forge/schema.ts +74 -0
- package/templates/vendor-access/src/policies.ts +11 -0
- package/templates/vendor-access/src/queries/listVendorAccessDashboard.ts +24 -0
- package/templates/vendor-access/src/queries/liveVendorAccessDashboard.ts +24 -0
- package/templates/vendor-access/tsconfig.json +17 -0
- package/templates/vendor-access/web/index.html +13 -0
- package/templates/vendor-access/web/package.json +21 -0
- package/templates/vendor-access/web/src/App.tsx +573 -0
- package/templates/vendor-access/web/src/lib/forge.ts +20 -0
- package/templates/vendor-access/web/src/main.tsx +205 -0
- package/templates/vendor-access/web/src/styles.css +682 -0
- package/templates/vendor-access/web/tsconfig.json +18 -0
- package/templates/vendor-access/web/vite.config.ts +36 -0
- package/src/forge/_generated/actionSubscriptions.json +0 -2
- package/src/forge/_generated/actionSubscriptions.ts +0 -10
- package/src/forge/_generated/agentAdapterManifest.json +0 -2
- package/src/forge/_generated/agentAdapterManifest.ts +0 -73
- package/src/forge/_generated/agentContract.json +0 -2
- package/src/forge/_generated/agentContract.ts +0 -7829
- package/src/forge/_generated/agentQuickstart.md +0 -34
- package/src/forge/_generated/agentTools.json +0 -2
- package/src/forge/_generated/agentTools.md +0 -16
- package/src/forge/_generated/agentTools.ts +0 -12
- package/src/forge/_generated/aiContext.ts +0 -125
- package/src/forge/_generated/aiModels.json +0 -2
- package/src/forge/_generated/aiModels.ts +0 -51
- package/src/forge/_generated/aiProviders.json +0 -2
- package/src/forge/_generated/aiProviders.ts +0 -23
- package/src/forge/_generated/aiRegistry.json +0 -2
- package/src/forge/_generated/aiRegistry.ts +0 -31
- package/src/forge/_generated/api.json +0 -2
- package/src/forge/_generated/api.ts +0 -8
- package/src/forge/_generated/appGraph.json +0 -2
- package/src/forge/_generated/appGraph.ts +0 -14937
- package/src/forge/_generated/appMap.md +0 -55
- package/src/forge/_generated/artifactManifest.json +0 -2
- package/src/forge/_generated/artifactManifest.ts +0 -7
- package/src/forge/_generated/authClaims.json +0 -2
- package/src/forge/_generated/authClaims.ts +0 -13
- package/src/forge/_generated/authConfig.json +0 -2
- package/src/forge/_generated/authConfig.ts +0 -17
- package/src/forge/_generated/authContext.ts +0 -23
- package/src/forge/_generated/authRegistry.json +0 -2
- package/src/forge/_generated/authRegistry.ts +0 -25
- package/src/forge/_generated/buildInfo.json +0 -2
- package/src/forge/_generated/buildInfo.ts +0 -9
- package/src/forge/_generated/capabilityMap.json +0 -2
- package/src/forge/_generated/capabilityMap.md +0 -15
- package/src/forge/_generated/capabilityMap.ts +0 -17
- package/src/forge/_generated/client.ts +0 -282
- package/src/forge/_generated/clientApi.ts +0 -9
- package/src/forge/_generated/clientManifest.json +0 -2
- package/src/forge/_generated/clientManifest.ts +0 -39
- package/src/forge/_generated/clientTypes.ts +0 -78
- package/src/forge/_generated/configRegistry.json +0 -2
- package/src/forge/_generated/configRegistry.ts +0 -4
- package/src/forge/_generated/dataGraph.json +0 -2
- package/src/forge/_generated/dataGraph.ts +0 -8
- package/src/forge/_generated/db.json +0 -2
- package/src/forge/_generated/db.ts +0 -2
- package/src/forge/_generated/dbSecurityManifest.json +0 -2
- package/src/forge/_generated/dbSecurityManifest.ts +0 -15
- package/src/forge/_generated/dbSessionContext.json +0 -2
- package/src/forge/_generated/dbSessionContext.ts +0 -39
- package/src/forge/_generated/deployManifest.json +0 -2
- package/src/forge/_generated/deployManifest.ts +0 -14
- package/src/forge/_generated/devManifest.json +0 -2
- package/src/forge/_generated/devManifest.ts +0 -62
- package/src/forge/_generated/envSchema.json +0 -2
- package/src/forge/_generated/envSchema.ts +0 -59
- package/src/forge/_generated/frontendGraph.json +0 -2
- package/src/forge/_generated/frontendGraph.ts +0 -27
- package/src/forge/_generated/importGuards.json +0 -2
- package/src/forge/_generated/importGuards.ts +0 -686
- package/src/forge/_generated/index.ts +0 -68
- package/src/forge/_generated/liveProductionManifest.json +0 -2
- package/src/forge/_generated/liveProductionManifest.ts +0 -23
- package/src/forge/_generated/liveProtocol.json +0 -2
- package/src/forge/_generated/liveProtocol.ts +0 -21
- package/src/forge/_generated/liveQueryRegistry.json +0 -2
- package/src/forge/_generated/liveQueryRegistry.ts +0 -9
- package/src/forge/_generated/liveTransportConfig.json +0 -2
- package/src/forge/_generated/liveTransportConfig.ts +0 -19
- package/src/forge/_generated/makeRegistry.json +0 -2
- package/src/forge/_generated/makeRegistry.ts +0 -177
- package/src/forge/_generated/makeTemplates.json +0 -2
- package/src/forge/_generated/makeTemplates.ts +0 -66
- package/src/forge/_generated/mockMap.json +0 -2
- package/src/forge/_generated/mockMap.ts +0 -7
- package/src/forge/_generated/operationPlaybooks.md +0 -167
- package/src/forge/_generated/packageGraph.json +0 -2
- package/src/forge/_generated/packageGraph.ts +0 -249334
- package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
- package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
- package/src/forge/_generated/permissionMatrix.json +0 -2
- package/src/forge/_generated/permissionMatrix.ts +0 -7
- package/src/forge/_generated/policyRegistry.json +0 -2
- package/src/forge/_generated/policyRegistry.ts +0 -11
- package/src/forge/_generated/queryRegistry.json +0 -2
- package/src/forge/_generated/queryRegistry.ts +0 -9
- package/src/forge/_generated/react.d.ts +0 -22
- package/src/forge/_generated/react.ts +0 -29
- package/src/forge/_generated/reactManifest.json +0 -2
- package/src/forge/_generated/reactManifest.ts +0 -19
- package/src/forge/_generated/rlsPolicies.json +0 -2
- package/src/forge/_generated/rlsPolicies.sql +0 -34
- package/src/forge/_generated/rlsPolicies.ts +0 -6
- package/src/forge/_generated/runtimeGraph.json +0 -2
- package/src/forge/_generated/runtimeGraph.ts +0 -8
- package/src/forge/_generated/runtimeMatrix.json +0 -2
- package/src/forge/_generated/runtimeMatrix.ts +0 -334130
- package/src/forge/_generated/runtimeRegistry.ts +0 -2
- package/src/forge/_generated/runtimeRules.md +0 -91
- package/src/forge/_generated/secretRegistry.json +0 -2
- package/src/forge/_generated/secretRegistry.ts +0 -50
- package/src/forge/_generated/secretsContext.ts +0 -11
- package/src/forge/_generated/serverApi.ts +0 -10
- package/src/forge/_generated/sourceMapManifest.json +0 -2
- package/src/forge/_generated/sourceMapManifest.ts +0 -7
- package/src/forge/_generated/sqlPlan.json +0 -2
- package/src/forge/_generated/sqlPlan.ts +0 -88
- package/src/forge/_generated/subscriptionManifest.json +0 -2
- package/src/forge/_generated/subscriptionManifest.ts +0 -7
- package/src/forge/_generated/symbolicationManifest.json +0 -2
- package/src/forge/_generated/symbolicationManifest.ts +0 -17
- package/src/forge/_generated/telemetryRegistry.json +0 -2
- package/src/forge/_generated/telemetryRegistry.ts +0 -9
- package/src/forge/_generated/telemetrySinks.json +0 -2
- package/src/forge/_generated/telemetrySinks.ts +0 -11
- package/src/forge/_generated/tenantScope.json +0 -2
- package/src/forge/_generated/tenantScope.ts +0 -8
- package/src/forge/_generated/testGraph.json +0 -2
- package/src/forge/_generated/testGraph.ts +0 -3452
- package/src/forge/_generated/testPlanRegistry.json +0 -2
- package/src/forge/_generated/testPlanRegistry.ts +0 -33
- package/src/forge/_generated/uiRoutes.json +0 -2
- package/src/forge/_generated/uiRoutes.ts +0 -16
- package/src/forge/_generated/uiScenarios.json +0 -2
- package/src/forge/_generated/uiScenarios.ts +0 -30
- package/src/forge/_generated/uiTestManifest.json +0 -2
- package/src/forge/_generated/uiTestManifest.ts +0 -27
- package/src/forge/_generated/workflowRegistry.json +0 -2
- package/src/forge/_generated/workflowRegistry.ts +0 -9
- package/src/forge/_generated/workflowSubscriptions.json +0 -2
- package/src/forge/_generated/workflowSubscriptions.ts +0 -10
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { DbAdapter, DbTransaction } from "../runtime/db/adapter.ts";
|
|
3
|
+
import { digestCanonical, sha256Digest, stableStringify } from "./canonical.ts";
|
|
4
|
+
import { AgentFabricError } from "./errors.ts";
|
|
5
|
+
import type { Digest } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
/** A candidate is data. This registry never loads or executes its artifact. */
|
|
8
|
+
export interface ExtensionCandidate {
|
|
9
|
+
extensionKey: string;
|
|
10
|
+
artifactDigest: Digest;
|
|
11
|
+
manifestDigest: Digest;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ExtensionVersion extends ExtensionCandidate {
|
|
15
|
+
versionId: string;
|
|
16
|
+
registeredAt: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FixedEvaluationSuite {
|
|
20
|
+
suiteId: string;
|
|
21
|
+
suiteDigest: Digest;
|
|
22
|
+
caseIds: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EvaluationCaseEvidence {
|
|
26
|
+
caseId: string;
|
|
27
|
+
passed: boolean;
|
|
28
|
+
evidenceDigest: Digest;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface EvaluationRecord {
|
|
32
|
+
versionId: string;
|
|
33
|
+
suiteId: string;
|
|
34
|
+
suiteDigest: Digest;
|
|
35
|
+
state: "running" | "passed" | "failed" | "inconclusive";
|
|
36
|
+
cases: readonly EvaluationCaseEvidence[];
|
|
37
|
+
evidenceDigest: Digest | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type EvolutionDecisionAction = "canary" | "promote" | "rollback" | "revoke";
|
|
41
|
+
export type EvolutionChannel = "canary" | "stable";
|
|
42
|
+
|
|
43
|
+
export interface EvolutionVersionStatus {
|
|
44
|
+
version: ExtensionVersion;
|
|
45
|
+
evaluation: EvaluationRecord | null;
|
|
46
|
+
revoked: boolean;
|
|
47
|
+
channels: readonly EvolutionChannel[];
|
|
48
|
+
lastDecision: { action: EvolutionDecisionAction; decidedAt: number; verifierId: string } | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface OwnerDecisionChallenge {
|
|
52
|
+
decisionNonce: string;
|
|
53
|
+
action: EvolutionDecisionAction;
|
|
54
|
+
versionId: string;
|
|
55
|
+
extensionKey: string;
|
|
56
|
+
expectedSelection: string | null;
|
|
57
|
+
evaluationDigest: Digest | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Must be backed by trusted owner ingress, separate from CLI/MCP/model input. */
|
|
61
|
+
export interface EvolutionOwnerVerifier {
|
|
62
|
+
verify(challenge: Readonly<OwnerDecisionChallenge>): Promise<{
|
|
63
|
+
verifierId: string;
|
|
64
|
+
challengeDigest: Digest;
|
|
65
|
+
evidenceDigest: Digest;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface EvolutionRegistryOptions {
|
|
70
|
+
adapter: DbAdapter;
|
|
71
|
+
suite: FixedEvaluationSuite;
|
|
72
|
+
evaluator: (version: Readonly<ExtensionVersion>, suite: Readonly<FixedEvaluationSuite>) => Promise<readonly EvaluationCaseEvidence[]>;
|
|
73
|
+
ownerVerifier: EvolutionOwnerVerifier;
|
|
74
|
+
now?: () => number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/u;
|
|
78
|
+
const KEY = /^[a-z][a-z0-9._-]{0,63}$/u;
|
|
79
|
+
const VERSION = /^extension:sha256:[0-9a-f]{64}$/u;
|
|
80
|
+
const ATTEMPT = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/u;
|
|
81
|
+
|
|
82
|
+
function invalid(message: string): never { throw new AgentFabricError("AF_INVALID_STATE", message); }
|
|
83
|
+
function conflict(message: string): never { throw new AgentFabricError("AF_CONFLICT", message); }
|
|
84
|
+
function validDigest(value: unknown): value is Digest { return typeof value === "string" && DIGEST.test(value); }
|
|
85
|
+
function validTime(value: number): boolean { return Number.isSafeInteger(value) && value >= 0; }
|
|
86
|
+
function versionIdFor(candidate: ExtensionCandidate): string {
|
|
87
|
+
return `extension:${digestCanonical(candidate, sha256Digest)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const SCHEMA = [
|
|
91
|
+
`CREATE TABLE IF NOT EXISTS _forge_fabric_extension_versions (
|
|
92
|
+
version_id TEXT PRIMARY KEY, extension_key TEXT NOT NULL,
|
|
93
|
+
artifact_digest TEXT NOT NULL, manifest_digest TEXT NOT NULL,
|
|
94
|
+
registered_at BIGINT NOT NULL)`,
|
|
95
|
+
`CREATE TABLE IF NOT EXISTS _forge_fabric_extension_evaluations (
|
|
96
|
+
version_id TEXT PRIMARY KEY, suite_id TEXT NOT NULL, suite_digest TEXT NOT NULL,
|
|
97
|
+
state TEXT NOT NULL CHECK (state IN ('running','passed','failed','inconclusive')),
|
|
98
|
+
cases_json TEXT NOT NULL, evidence_digest TEXT)`,
|
|
99
|
+
`CREATE TABLE IF NOT EXISTS _forge_fabric_extension_decisions (
|
|
100
|
+
decision_sequence BIGSERIAL PRIMARY KEY, version_id TEXT NOT NULL,
|
|
101
|
+
extension_key TEXT NOT NULL, action TEXT NOT NULL,
|
|
102
|
+
challenge_digest TEXT NOT NULL, owner_evidence_digest TEXT NOT NULL,
|
|
103
|
+
verifier_id TEXT NOT NULL, decided_at BIGINT NOT NULL)`,
|
|
104
|
+
`CREATE TABLE IF NOT EXISTS _forge_fabric_extension_selections (
|
|
105
|
+
extension_key TEXT NOT NULL, channel TEXT NOT NULL,
|
|
106
|
+
version_id TEXT, PRIMARY KEY (extension_key, channel))`,
|
|
107
|
+
`CREATE TABLE IF NOT EXISTS _forge_fabric_extension_revocations (
|
|
108
|
+
version_id TEXT PRIMARY KEY, decided_at BIGINT NOT NULL)`,
|
|
109
|
+
`CREATE TABLE IF NOT EXISTS _forge_fabric_extension_attempts (
|
|
110
|
+
attempt_id TEXT PRIMARY KEY, version_id TEXT NOT NULL,
|
|
111
|
+
extension_key TEXT NOT NULL, channel TEXT NOT NULL, bound_at BIGINT NOT NULL)`,
|
|
112
|
+
] as const;
|
|
113
|
+
|
|
114
|
+
/** Serialized single-owner PGlite registry. All state mutations commit before acknowledgement. */
|
|
115
|
+
export class LocalEvolutionRegistry {
|
|
116
|
+
private schemaReady?: Promise<void>;
|
|
117
|
+
private tail: Promise<void> = Promise.resolve();
|
|
118
|
+
private readonly now: () => number;
|
|
119
|
+
private readonly suite: FixedEvaluationSuite;
|
|
120
|
+
|
|
121
|
+
constructor(private readonly options: EvolutionRegistryOptions) {
|
|
122
|
+
if (options.adapter.kind !== "pglite") invalid("Evolution registry requires local PGlite");
|
|
123
|
+
const suite = structuredClone(options.suite);
|
|
124
|
+
if (!KEY.test(suite.suiteId) || !validDigest(suite.suiteDigest) ||
|
|
125
|
+
suite.caseIds.length === 0 || suite.caseIds.length > 64 ||
|
|
126
|
+
new Set(suite.caseIds).size !== suite.caseIds.length ||
|
|
127
|
+
suite.caseIds.some((id) => !KEY.test(id)) ||
|
|
128
|
+
suite.suiteDigest !== digestCanonical({ suiteId: suite.suiteId, caseIds: suite.caseIds }, sha256Digest)) {
|
|
129
|
+
invalid("Invalid fixed evaluation suite");
|
|
130
|
+
}
|
|
131
|
+
this.suite = suite;
|
|
132
|
+
this.now = options.now ?? Date.now;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async ready(): Promise<void> {
|
|
136
|
+
this.schemaReady ??= (async () => {
|
|
137
|
+
for (const statement of SCHEMA) await this.options.adapter.query(statement);
|
|
138
|
+
})();
|
|
139
|
+
await this.schemaReady;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async serialized<T>(fn: () => Promise<T>): Promise<T> {
|
|
143
|
+
const previous = this.tail;
|
|
144
|
+
let release!: () => void;
|
|
145
|
+
this.tail = new Promise<void>((resolve) => { release = resolve; });
|
|
146
|
+
await previous;
|
|
147
|
+
try { await this.ready(); return await fn(); } finally { release(); }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async transaction<T>(fn: (tx: DbTransaction) => Promise<T>): Promise<T> {
|
|
151
|
+
const tx = await this.options.adapter.begin();
|
|
152
|
+
try {
|
|
153
|
+
const result = await fn(tx);
|
|
154
|
+
await tx.commit();
|
|
155
|
+
return result;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
await tx.rollback().catch(() => undefined);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async register(candidate: ExtensionCandidate): Promise<ExtensionVersion> {
|
|
163
|
+
if (!candidate || !KEY.test(candidate.extensionKey) || !validDigest(candidate.artifactDigest) ||
|
|
164
|
+
!validDigest(candidate.manifestDigest)) invalid("Invalid extension candidate");
|
|
165
|
+
const exactCandidate: ExtensionCandidate = {
|
|
166
|
+
extensionKey: candidate.extensionKey,
|
|
167
|
+
artifactDigest: candidate.artifactDigest,
|
|
168
|
+
manifestDigest: candidate.manifestDigest,
|
|
169
|
+
};
|
|
170
|
+
const versionId = versionIdFor(exactCandidate);
|
|
171
|
+
const registeredAt = this.now();
|
|
172
|
+
if (!validTime(registeredAt)) invalid("Invalid registry time");
|
|
173
|
+
return this.serialized(async () => {
|
|
174
|
+
await this.options.adapter.query(
|
|
175
|
+
`INSERT INTO _forge_fabric_extension_versions
|
|
176
|
+
(version_id, extension_key, artifact_digest, manifest_digest, registered_at)
|
|
177
|
+
VALUES ($1,$2,$3,$4,$5) ON CONFLICT (version_id) DO NOTHING`,
|
|
178
|
+
[versionId, exactCandidate.extensionKey, exactCandidate.artifactDigest,
|
|
179
|
+
exactCandidate.manifestDigest, registeredAt],
|
|
180
|
+
);
|
|
181
|
+
return (await this.getVersion(versionId))!;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async getVersion(versionId: string): Promise<ExtensionVersion | null> {
|
|
186
|
+
if (!VERSION.test(versionId)) invalid("Invalid extension version ID");
|
|
187
|
+
await this.ready();
|
|
188
|
+
const rows = (await this.options.adapter.query(
|
|
189
|
+
`SELECT version_id, extension_key, artifact_digest, manifest_digest, registered_at
|
|
190
|
+
FROM _forge_fabric_extension_versions WHERE version_id=$1`, [versionId],
|
|
191
|
+
)).rows;
|
|
192
|
+
const row = rows[0];
|
|
193
|
+
if (!row) return null;
|
|
194
|
+
const candidate = {
|
|
195
|
+
extensionKey: row.extension_key as string,
|
|
196
|
+
artifactDigest: row.artifact_digest as Digest,
|
|
197
|
+
manifestDigest: row.manifest_digest as Digest,
|
|
198
|
+
};
|
|
199
|
+
const registeredAt = Number(row.registered_at);
|
|
200
|
+
if (!KEY.test(candidate.extensionKey) || !validDigest(candidate.artifactDigest) ||
|
|
201
|
+
!validDigest(candidate.manifestDigest) || !validTime(registeredAt) ||
|
|
202
|
+
versionIdFor(candidate) !== row.version_id) invalid("Stored extension version is inconsistent");
|
|
203
|
+
return { ...candidate, versionId, registeredAt };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async evaluate(versionId: string): Promise<EvaluationRecord> {
|
|
207
|
+
const version = await this.getVersion(versionId);
|
|
208
|
+
if (!version) throw new AgentFabricError("AF_NOT_FOUND", "Extension version not found");
|
|
209
|
+
const suite = this.suite;
|
|
210
|
+
await this.serialized(async () => {
|
|
211
|
+
await this.transaction(async (tx) => {
|
|
212
|
+
const existing = (await tx.query(
|
|
213
|
+
`SELECT version_id FROM _forge_fabric_extension_evaluations WHERE version_id=$1`, [versionId],
|
|
214
|
+
)).rows[0];
|
|
215
|
+
if (existing) conflict("Extension evaluation already started");
|
|
216
|
+
await tx.query(
|
|
217
|
+
`INSERT INTO _forge_fabric_extension_evaluations
|
|
218
|
+
(version_id,suite_id,suite_digest,state,cases_json,evidence_digest)
|
|
219
|
+
VALUES ($1,$2,$3,'running','[]',NULL)`, [versionId, suite.suiteId, suite.suiteDigest],
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
let cases: readonly EvaluationCaseEvidence[] = [];
|
|
224
|
+
let state: EvaluationRecord["state"] = "inconclusive";
|
|
225
|
+
try {
|
|
226
|
+
const result = await this.options.evaluator(structuredClone(version), structuredClone(suite));
|
|
227
|
+
if (result.length !== suite.caseIds.length || result.length > 64 ||
|
|
228
|
+
result.some((item, index) => !item || item.caseId !== suite.caseIds[index] ||
|
|
229
|
+
typeof item.passed !== "boolean" || !validDigest(item.evidenceDigest))) {
|
|
230
|
+
invalid("Evaluator returned evidence outside the fixed suite");
|
|
231
|
+
}
|
|
232
|
+
cases = structuredClone(result);
|
|
233
|
+
state = cases.every((item) => item.passed) ? "passed" : "failed";
|
|
234
|
+
} catch {
|
|
235
|
+
// An evaluator error is not evidence of a failed case. It remains inconclusive.
|
|
236
|
+
}
|
|
237
|
+
const evidenceDigest = state === "inconclusive" ? null : digestCanonical(
|
|
238
|
+
{ versionId, suiteId: suite.suiteId, suiteDigest: suite.suiteDigest, cases }, sha256Digest,
|
|
239
|
+
);
|
|
240
|
+
await this.serialized(async () => {
|
|
241
|
+
const update = await this.options.adapter.query(
|
|
242
|
+
`UPDATE _forge_fabric_extension_evaluations
|
|
243
|
+
SET state=$2,cases_json=$3,evidence_digest=$4
|
|
244
|
+
WHERE version_id=$1 AND state='running'`,
|
|
245
|
+
[versionId, state, stableStringify(cases), evidenceDigest],
|
|
246
|
+
);
|
|
247
|
+
if (update.rowCount !== 1) conflict("Evaluation state changed before completion");
|
|
248
|
+
});
|
|
249
|
+
return { versionId, suiteId: suite.suiteId, suiteDigest: suite.suiteDigest, state, cases, evidenceDigest };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async getEvaluation(versionId: string): Promise<EvaluationRecord | null> {
|
|
253
|
+
if (!VERSION.test(versionId)) invalid("Invalid extension version ID");
|
|
254
|
+
await this.ready();
|
|
255
|
+
const row = (await this.options.adapter.query(
|
|
256
|
+
`SELECT suite_id,suite_digest,state,cases_json,evidence_digest
|
|
257
|
+
FROM _forge_fabric_extension_evaluations WHERE version_id=$1`, [versionId],
|
|
258
|
+
)).rows[0];
|
|
259
|
+
if (!row) return null;
|
|
260
|
+
let cases: EvaluationCaseEvidence[];
|
|
261
|
+
try { cases = JSON.parse(String(row.cases_json)) as EvaluationCaseEvidence[]; }
|
|
262
|
+
catch { return invalid("Stored evaluation is not JSON"); }
|
|
263
|
+
const state = row.state as EvaluationRecord["state"];
|
|
264
|
+
const suite = this.suite;
|
|
265
|
+
if (row.suite_id !== suite.suiteId || row.suite_digest !== suite.suiteDigest ||
|
|
266
|
+
!["running", "passed", "failed", "inconclusive"].includes(state) ||
|
|
267
|
+
!Array.isArray(cases) ||
|
|
268
|
+
(state === "running" || state === "inconclusive" ? cases.length !== 0 || row.evidence_digest !== null :
|
|
269
|
+
cases.length !== suite.caseIds.length || cases.some((item, index) =>
|
|
270
|
+
item?.caseId !== suite.caseIds[index] || typeof item.passed !== "boolean" ||
|
|
271
|
+
!validDigest(item.evidenceDigest)) ||
|
|
272
|
+
(state === "passed") !== cases.every((item) => item.passed) ||
|
|
273
|
+
row.evidence_digest !== digestCanonical(
|
|
274
|
+
{ versionId, suiteId: suite.suiteId, suiteDigest: suite.suiteDigest, cases }, sha256Digest,
|
|
275
|
+
))) invalid("Stored evaluation evidence is inconsistent");
|
|
276
|
+
return { versionId, suiteId: suite.suiteId, suiteDigest: suite.suiteDigest, state,
|
|
277
|
+
cases, evidenceDigest: row.evidence_digest as Digest | null };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async selected(extensionKey: string, channel: EvolutionChannel): Promise<string | null> {
|
|
281
|
+
if (!KEY.test(extensionKey) || !["canary", "stable"].includes(channel)) invalid("Invalid selection query");
|
|
282
|
+
await this.ready();
|
|
283
|
+
const row = (await this.options.adapter.query(
|
|
284
|
+
`SELECT version_id FROM _forge_fabric_extension_selections WHERE extension_key=$1 AND channel=$2`,
|
|
285
|
+
[extensionKey, channel],
|
|
286
|
+
)).rows[0];
|
|
287
|
+
const versionId = row?.version_id as string | null ?? null;
|
|
288
|
+
if (versionId && (await this.options.adapter.query(
|
|
289
|
+
`SELECT version_id FROM _forge_fabric_extension_revocations WHERE version_id=$1`, [versionId],
|
|
290
|
+
)).rows.length > 0) invalid("Selected extension is revoked");
|
|
291
|
+
return versionId;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Bounded owner status for one immutable version; no artifact bytes or decision secrets. */
|
|
295
|
+
async status(versionId: string): Promise<EvolutionVersionStatus | null> {
|
|
296
|
+
const version = await this.getVersion(versionId);
|
|
297
|
+
if (!version) return null;
|
|
298
|
+
const [evaluation, revokedRows, selectionRows, decisionRows] = await Promise.all([
|
|
299
|
+
this.getEvaluation(versionId),
|
|
300
|
+
this.options.adapter.query(`SELECT version_id FROM _forge_fabric_extension_revocations WHERE version_id=$1`, [versionId]),
|
|
301
|
+
this.options.adapter.query(`SELECT channel FROM _forge_fabric_extension_selections WHERE extension_key=$1 AND version_id=$2`,
|
|
302
|
+
[version.extensionKey, versionId]),
|
|
303
|
+
this.options.adapter.query(`SELECT action,decided_at,verifier_id FROM _forge_fabric_extension_decisions
|
|
304
|
+
WHERE version_id=$1 ORDER BY decision_sequence DESC LIMIT 1`, [versionId]),
|
|
305
|
+
]);
|
|
306
|
+
const channels = selectionRows.rows.map((row) => row.channel as EvolutionChannel);
|
|
307
|
+
if (channels.some((channel) => channel !== "canary" && channel !== "stable")) invalid("Stored channel is inconsistent");
|
|
308
|
+
const last = decisionRows.rows[0];
|
|
309
|
+
return { version, evaluation, revoked: revokedRows.rows.length > 0, channels,
|
|
310
|
+
lastDecision: last ? { action: last.action as EvolutionDecisionAction,
|
|
311
|
+
decidedAt: Number(last.decided_at), verifierId: String(last.verifier_id) } : null };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async decide(action: EvolutionDecisionAction, versionId: string, expectedSelection: string | null): Promise<void> {
|
|
315
|
+
if (!["canary", "promote", "rollback", "revoke"].includes(action) || !VERSION.test(versionId) ||
|
|
316
|
+
(expectedSelection !== null && !VERSION.test(expectedSelection))) invalid("Invalid evolution decision");
|
|
317
|
+
const version = await this.getVersion(versionId);
|
|
318
|
+
if (!version) throw new AgentFabricError("AF_NOT_FOUND", "Extension version not found");
|
|
319
|
+
const channel: EvolutionChannel = action === "canary" ? "canary" : "stable";
|
|
320
|
+
const evaluation = await this.getEvaluation(versionId);
|
|
321
|
+
if (action !== "revoke" && evaluation?.state !== "passed") {
|
|
322
|
+
conflict("Passing fixed-suite evaluation is required for selection");
|
|
323
|
+
}
|
|
324
|
+
const challenge: OwnerDecisionChallenge = {
|
|
325
|
+
decisionNonce: randomUUID(),
|
|
326
|
+
action, versionId, extensionKey: version.extensionKey, expectedSelection,
|
|
327
|
+
evaluationDigest: evaluation?.evidenceDigest ?? null,
|
|
328
|
+
};
|
|
329
|
+
const proof = await this.options.ownerVerifier.verify(structuredClone(challenge));
|
|
330
|
+
const challengeDigest = digestCanonical(challenge, sha256Digest);
|
|
331
|
+
if (!proof || !KEY.test(proof.verifierId) || proof.challengeDigest !== challengeDigest ||
|
|
332
|
+
!validDigest(proof.evidenceDigest)) invalid("Trusted owner verification did not bind this decision");
|
|
333
|
+
const decidedAt = this.now();
|
|
334
|
+
if (!validTime(decidedAt)) invalid("Invalid registry time");
|
|
335
|
+
await this.serialized(async () => this.transaction(async (tx) => {
|
|
336
|
+
const current = (await tx.query(
|
|
337
|
+
`SELECT version_id FROM _forge_fabric_extension_selections WHERE extension_key=$1 AND channel=$2`,
|
|
338
|
+
[version.extensionKey, channel],
|
|
339
|
+
)).rows[0]?.version_id as string | null ?? null;
|
|
340
|
+
if (current !== expectedSelection) conflict("Selection changed before owner decision");
|
|
341
|
+
const revoked = (await tx.query(
|
|
342
|
+
`SELECT version_id FROM _forge_fabric_extension_revocations WHERE version_id=$1`, [versionId],
|
|
343
|
+
)).rows.length > 0;
|
|
344
|
+
if (revoked) conflict("Revoked extension cannot be selected or decided again");
|
|
345
|
+
if (action !== "revoke") {
|
|
346
|
+
const storedEvaluation = (await tx.query(
|
|
347
|
+
`SELECT state,evidence_digest FROM _forge_fabric_extension_evaluations WHERE version_id=$1`,
|
|
348
|
+
[versionId],
|
|
349
|
+
)).rows[0];
|
|
350
|
+
if (storedEvaluation?.state !== "passed" ||
|
|
351
|
+
storedEvaluation.evidence_digest !== challenge.evaluationDigest) {
|
|
352
|
+
conflict("Passing fixed-suite evaluation is required for selection");
|
|
353
|
+
}
|
|
354
|
+
if (action === "rollback" && (await tx.query(
|
|
355
|
+
`SELECT version_id FROM _forge_fabric_extension_decisions
|
|
356
|
+
WHERE version_id=$1 AND action IN ('promote','rollback') LIMIT 1`, [versionId],
|
|
357
|
+
)).rows.length === 0) conflict("Rollback target was never a stable version");
|
|
358
|
+
}
|
|
359
|
+
await tx.query(
|
|
360
|
+
`INSERT INTO _forge_fabric_extension_decisions
|
|
361
|
+
(version_id,extension_key,action,challenge_digest,owner_evidence_digest,verifier_id,decided_at)
|
|
362
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
|
363
|
+
[versionId, version.extensionKey, action, challengeDigest, proof.evidenceDigest, proof.verifierId, decidedAt],
|
|
364
|
+
);
|
|
365
|
+
if (action === "revoke") {
|
|
366
|
+
await tx.query(`INSERT INTO _forge_fabric_extension_revocations (version_id,decided_at) VALUES ($1,$2)`,
|
|
367
|
+
[versionId, decidedAt]);
|
|
368
|
+
await tx.query(
|
|
369
|
+
`UPDATE _forge_fabric_extension_selections SET version_id=NULL
|
|
370
|
+
WHERE extension_key=$1 AND version_id=$2`, [version.extensionKey, versionId],
|
|
371
|
+
);
|
|
372
|
+
} else {
|
|
373
|
+
await tx.query(
|
|
374
|
+
`INSERT INTO _forge_fabric_extension_selections (extension_key,channel,version_id)
|
|
375
|
+
VALUES ($1,$2,$3) ON CONFLICT (extension_key,channel)
|
|
376
|
+
DO UPDATE SET version_id=EXCLUDED.version_id`,
|
|
377
|
+
[version.extensionKey, channel, versionId],
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async bindAttempt(attemptId: string, extensionKey: string, channel: EvolutionChannel): Promise<{
|
|
384
|
+
attemptId: string; versionId: string; extensionKey: string; channel: EvolutionChannel;
|
|
385
|
+
}> {
|
|
386
|
+
if (!ATTEMPT.test(attemptId) || !KEY.test(extensionKey) || !["canary", "stable"].includes(channel)) {
|
|
387
|
+
invalid("Invalid attempt binding");
|
|
388
|
+
}
|
|
389
|
+
const boundAt = this.now();
|
|
390
|
+
if (!validTime(boundAt)) invalid("Invalid registry time");
|
|
391
|
+
return this.serialized(async () => this.transaction(async (tx) => {
|
|
392
|
+
const old = (await tx.query(
|
|
393
|
+
`SELECT version_id,extension_key,channel FROM _forge_fabric_extension_attempts WHERE attempt_id=$1`, [attemptId],
|
|
394
|
+
)).rows[0];
|
|
395
|
+
if (old) {
|
|
396
|
+
conflict("Attempt is already bound; read its existing version instead of dispatching again");
|
|
397
|
+
}
|
|
398
|
+
const versionId = (await tx.query(
|
|
399
|
+
`SELECT version_id FROM _forge_fabric_extension_selections WHERE extension_key=$1 AND channel=$2`,
|
|
400
|
+
[extensionKey, channel],
|
|
401
|
+
)).rows[0]?.version_id as string | null;
|
|
402
|
+
if (!versionId) conflict("No extension selected on this channel");
|
|
403
|
+
if ((await tx.query(
|
|
404
|
+
`SELECT version_id FROM _forge_fabric_extension_revocations WHERE version_id=$1`, [versionId],
|
|
405
|
+
)).rows.length > 0) conflict("Revoked extension cannot be selected");
|
|
406
|
+
await tx.query(
|
|
407
|
+
`INSERT INTO _forge_fabric_extension_attempts
|
|
408
|
+
(attempt_id,version_id,extension_key,channel,bound_at) VALUES ($1,$2,$3,$4,$5)`,
|
|
409
|
+
[attemptId, versionId, extensionKey, channel, boundAt],
|
|
410
|
+
);
|
|
411
|
+
return { attemptId, versionId, extensionKey, channel };
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async getAttempt(attemptId: string): Promise<{ attemptId: string; versionId: string;
|
|
416
|
+
extensionKey: string; channel: EvolutionChannel } | null> {
|
|
417
|
+
if (!ATTEMPT.test(attemptId)) invalid("Invalid attempt identity");
|
|
418
|
+
await this.ready();
|
|
419
|
+
const row = (await this.options.adapter.query(
|
|
420
|
+
`SELECT version_id,extension_key,channel FROM _forge_fabric_extension_attempts WHERE attempt_id=$1`,
|
|
421
|
+
[attemptId],
|
|
422
|
+
)).rows[0];
|
|
423
|
+
if (!row) return null;
|
|
424
|
+
return { attemptId, versionId: row.version_id as string,
|
|
425
|
+
extensionKey: row.extension_key as string, channel: row.channel as EvolutionChannel };
|
|
426
|
+
}
|
|
427
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { createPgliteAdapter } from "../runtime/db/pglite-adapter.ts";
|
|
5
|
+
import type { DbAdapter } from "../runtime/db/adapter.ts";
|
|
6
|
+
import { digestCanonical, sha256Digest } from "./canonical.ts";
|
|
7
|
+
import { AgentFabricError } from "./errors.ts";
|
|
8
|
+
import { localEvolutionOwnerVerifier } from "./local-evolution-approval.ts";
|
|
9
|
+
import { LOCAL_ADAPTIVE_EXTENSION_KEY, assertLocalAdaptiveInputs,
|
|
10
|
+
evaluateLocalAdaptiveProfile, parseLocalAdaptiveInputProfile } from "./local-evolution-profile.ts";
|
|
11
|
+
import { LocalEvolutionRegistry, type EvolutionChannel, type EvolutionDecisionAction,
|
|
12
|
+
type EvolutionOwnerVerifier, type EvolutionVersionStatus, type FixedEvaluationSuite } from "./local-evolution-registry.ts";
|
|
13
|
+
import { localFabricPath } from "./local-paths.ts";
|
|
14
|
+
import type { Digest } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
const MAX_MANIFEST = 16 * 1024;
|
|
17
|
+
const MAX_ARTIFACT = 1024 * 1024;
|
|
18
|
+
const suiteId = "local-adaptive-input-profile-v1";
|
|
19
|
+
const caseIds = ["artifact-integrity", "manifest-integrity", "manifest-contract",
|
|
20
|
+
"profile-contract", "accept-valid-input", "reject-wrong-label", "reject-over-limit"] as const;
|
|
21
|
+
export const LOCAL_EVOLUTION_SUITE: FixedEvaluationSuite = {
|
|
22
|
+
suiteId, caseIds, suiteDigest: digestCanonical({ suiteId, caseIds }, sha256Digest),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
interface ExtensionManifest {
|
|
26
|
+
schemaVersion: 1;
|
|
27
|
+
extensionKey: string;
|
|
28
|
+
artifactPath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function invalid(message: string): never { throw new AgentFabricError("AF_INVALID_STATE", message); }
|
|
32
|
+
function digest(bytes: Buffer): Digest { return `sha256:${createHash("sha256").update(bytes).digest("hex")}`; }
|
|
33
|
+
function strictManifest(bytes: Buffer): ExtensionManifest {
|
|
34
|
+
let value: unknown;
|
|
35
|
+
try { value = JSON.parse(bytes.toString("utf8")); } catch { return invalid("Extension manifest is invalid JSON"); }
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) invalid("Extension manifest must be an object");
|
|
37
|
+
const item = value as Partial<ExtensionManifest>;
|
|
38
|
+
if (Object.keys(item).sort().join(",") !== "artifactPath,extensionKey,schemaVersion" ||
|
|
39
|
+
item.schemaVersion !== 1 || typeof item.extensionKey !== "string" || item.extensionKey !== LOCAL_ADAPTIVE_EXTENSION_KEY ||
|
|
40
|
+
typeof item.artifactPath !== "string" || !/^[A-Za-z0-9_./-]{1,256}$/u.test(item.artifactPath) ||
|
|
41
|
+
item.artifactPath.split("/").some((part) => part === ".." || part === "." || part === "") ||
|
|
42
|
+
!item.artifactPath.endsWith(".json")) {
|
|
43
|
+
invalid("Extension manifest is outside the local v1 contract");
|
|
44
|
+
}
|
|
45
|
+
return item as ExtensionManifest;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readBounded(path: string, max: number): Buffer {
|
|
49
|
+
const size = statSync(path).size;
|
|
50
|
+
if (!Number.isSafeInteger(size) || size < 1 || size > max || !statSync(path).isFile()) invalid("Extension file size or type is invalid");
|
|
51
|
+
const bytes = readFileSync(path);
|
|
52
|
+
if (bytes.length !== size) invalid("Extension file changed during read");
|
|
53
|
+
return bytes;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sourcePath(root: string, path: string): string {
|
|
57
|
+
const full = realpathSync(resolve(root, path));
|
|
58
|
+
const rel = relative(root, full);
|
|
59
|
+
if (!rel || rel.startsWith("..") || isAbsolute(rel)) invalid("Extension source must be inside this repository");
|
|
60
|
+
return full;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function storedPath(root: string, kind: "artifact" | "manifest", digestValue: Digest): string {
|
|
64
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(digestValue)) invalid("Invalid stored extension digest");
|
|
65
|
+
return localFabricPath(root, "evolution", kind === "artifact" ? "artifacts" : "manifests", `${digestValue.slice(7)}.${kind === "artifact" ? "bin" : "json"}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function store(root: string, kind: "artifact" | "manifest", bytes: Buffer): Digest {
|
|
69
|
+
const value = digest(bytes);
|
|
70
|
+
const path = storedPath(root, kind, value);
|
|
71
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
72
|
+
if (!existsSync(path)) {
|
|
73
|
+
try { writeFileSync(path, bytes, { flag: "wx", mode: 0o600 }); }
|
|
74
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; }
|
|
75
|
+
}
|
|
76
|
+
if (!readBounded(path, kind === "artifact" ? MAX_ARTIFACT : MAX_MANIFEST).equals(bytes)) {
|
|
77
|
+
invalid("Stored immutable extension bytes differ from candidate");
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class LocalEvolutionService {
|
|
83
|
+
private constructor(readonly repositoryRoot: string, private readonly adapter: DbAdapter,
|
|
84
|
+
readonly registry: LocalEvolutionRegistry) {}
|
|
85
|
+
|
|
86
|
+
static async open(repositoryRoot: string, ownerVerifier: EvolutionOwnerVerifier = localEvolutionOwnerVerifier()): Promise<LocalEvolutionService> {
|
|
87
|
+
const root = realpathSync(repositoryRoot);
|
|
88
|
+
const adapter = await createPgliteAdapter(localFabricPath(root, "evolution", "pglite"));
|
|
89
|
+
const evaluator = async (version: { artifactDigest: Digest; manifestDigest: Digest; extensionKey: string }) => {
|
|
90
|
+
let artifact: Buffer | null = null;
|
|
91
|
+
let manifest: Buffer | null = null;
|
|
92
|
+
try { artifact = readBounded(storedPath(root, "artifact", version.artifactDigest), MAX_ARTIFACT); } catch { /* fixed case fails */ }
|
|
93
|
+
try { manifest = readBounded(storedPath(root, "manifest", version.manifestDigest), MAX_MANIFEST); } catch { /* fixed case fails */ }
|
|
94
|
+
const artifactObserved = artifact ? digest(artifact) : sha256Digest("missing-artifact");
|
|
95
|
+
const manifestObserved = manifest ? digest(manifest) : sha256Digest("missing-manifest");
|
|
96
|
+
let contract = false;
|
|
97
|
+
if (manifest) {
|
|
98
|
+
try { contract = strictManifest(manifest).extensionKey === version.extensionKey; } catch { /* fixed case fails */ }
|
|
99
|
+
}
|
|
100
|
+
const profile = artifact ? evaluateLocalAdaptiveProfile(artifact) :
|
|
101
|
+
{ contract: false, acceptsValid: false, rejectsWrongLabel: false, rejectsOverLimit: false };
|
|
102
|
+
return [
|
|
103
|
+
{ caseId: caseIds[0], passed: artifactObserved === version.artifactDigest, evidenceDigest: artifactObserved },
|
|
104
|
+
{ caseId: caseIds[1], passed: manifestObserved === version.manifestDigest, evidenceDigest: manifestObserved },
|
|
105
|
+
{ caseId: caseIds[2], passed: contract, evidenceDigest: digestCanonical({ manifestDigest: manifestObserved, extensionKey: version.extensionKey, contract }, sha256Digest) },
|
|
106
|
+
...([
|
|
107
|
+
["profile-contract", profile.contract],
|
|
108
|
+
["accept-valid-input", profile.acceptsValid],
|
|
109
|
+
["reject-wrong-label", profile.rejectsWrongLabel],
|
|
110
|
+
["reject-over-limit", profile.rejectsOverLimit],
|
|
111
|
+
] as const).map(([caseId, passed]) => ({
|
|
112
|
+
caseId, passed, evidenceDigest: digestCanonical({ artifactDigest: artifactObserved, caseId, passed }, sha256Digest),
|
|
113
|
+
})),
|
|
114
|
+
];
|
|
115
|
+
};
|
|
116
|
+
try { return new LocalEvolutionService(root, adapter, new LocalEvolutionRegistry({
|
|
117
|
+
adapter, suite: LOCAL_EVOLUTION_SUITE, evaluator, ownerVerifier,
|
|
118
|
+
})); } catch (error) { await adapter.close(); throw error; }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async close(): Promise<void> { await this.adapter.close(); }
|
|
122
|
+
|
|
123
|
+
async register(manifestPath: string): Promise<EvolutionVersionStatus> {
|
|
124
|
+
const manifest = readBounded(sourcePath(this.repositoryRoot, manifestPath), MAX_MANIFEST);
|
|
125
|
+
const parsed = strictManifest(manifest);
|
|
126
|
+
const artifact = readBounded(sourcePath(this.repositoryRoot, parsed.artifactPath), MAX_ARTIFACT);
|
|
127
|
+
const artifactDigest = store(this.repositoryRoot, "artifact", artifact);
|
|
128
|
+
const manifestDigest = store(this.repositoryRoot, "manifest", manifest);
|
|
129
|
+
const version = await this.registry.register({ extensionKey: parsed.extensionKey, artifactDigest, manifestDigest });
|
|
130
|
+
return (await this.registry.status(version.versionId))!;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async status(versionId: string): Promise<EvolutionVersionStatus> {
|
|
134
|
+
const result = await this.registry.status(versionId);
|
|
135
|
+
if (!result) throw new AgentFabricError("AF_NOT_FOUND", "Extension version not found");
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async evaluate(versionId: string): Promise<EvolutionVersionStatus> {
|
|
140
|
+
await this.registry.evaluate(versionId);
|
|
141
|
+
return this.status(versionId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async decide(action: EvolutionDecisionAction, versionId: string): Promise<EvolutionVersionStatus> {
|
|
145
|
+
// Adaptive execution holds this same lock from profile readback through worker
|
|
146
|
+
// dispatch. A concurrent promotion or revocation must not pass between those
|
|
147
|
+
// two operations and silently change what the owner approved.
|
|
148
|
+
const lockPath = localFabricPath(this.repositoryRoot, "adaptive-lock");
|
|
149
|
+
let descriptor: number;
|
|
150
|
+
try { descriptor = openSync(lockPath, "wx", 0o600); }
|
|
151
|
+
catch { throw new AgentFabricError("AF_CONFLICT", "Adaptive owner is busy; inspect adaptive status before changing its profile"); }
|
|
152
|
+
try {
|
|
153
|
+
const before = await this.status(versionId);
|
|
154
|
+
const channel: EvolutionChannel = action === "canary" ? "canary" : "stable";
|
|
155
|
+
const expected = await this.registry.selected(before.version.extensionKey, channel);
|
|
156
|
+
await this.registry.decide(action, versionId, expected);
|
|
157
|
+
return this.status(versionId);
|
|
158
|
+
} finally { closeSync(descriptor); unlinkSync(lockPath); }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Loading returns verified bytes only for the currently selected, evaluated, non-revoked version. */
|
|
162
|
+
async loadSelected(extensionKey: string, channel: EvolutionChannel): Promise<{
|
|
163
|
+
versionId: string; artifact: Buffer; manifest: ExtensionManifest;
|
|
164
|
+
}> {
|
|
165
|
+
const versionId = await this.registry.selected(extensionKey, channel);
|
|
166
|
+
if (!versionId) throw new AgentFabricError("AF_CONFLICT", "No selected extension version on this channel");
|
|
167
|
+
const status = await this.status(versionId);
|
|
168
|
+
if (status.revoked || !status.channels.includes(channel) || status.evaluation?.state !== "passed" ||
|
|
169
|
+
status.evaluation.suiteDigest !== LOCAL_EVOLUTION_SUITE.suiteDigest) {
|
|
170
|
+
throw new AgentFabricError("AF_CONFLICT", "Extension version is not loadable");
|
|
171
|
+
}
|
|
172
|
+
const artifact = readBounded(storedPath(this.repositoryRoot, "artifact", status.version.artifactDigest), MAX_ARTIFACT);
|
|
173
|
+
const manifestBytes = readBounded(storedPath(this.repositoryRoot, "manifest", status.version.manifestDigest), MAX_MANIFEST);
|
|
174
|
+
if (digest(artifact) !== status.version.artifactDigest || digest(manifestBytes) !== status.version.manifestDigest) {
|
|
175
|
+
invalid("Stored immutable extension bytes changed");
|
|
176
|
+
}
|
|
177
|
+
const manifest = strictManifest(manifestBytes);
|
|
178
|
+
if (manifest.extensionKey !== extensionKey) invalid("Stored extension manifest key changed");
|
|
179
|
+
// Guard a revocation racing with a file read before handing bytes to the caller.
|
|
180
|
+
if (await this.registry.selected(extensionKey, channel) !== versionId) {
|
|
181
|
+
throw new AgentFabricError("AF_CONFLICT", "Extension selection changed during load");
|
|
182
|
+
}
|
|
183
|
+
return { versionId, artifact, manifest };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Bind one attempt to the selected immutable profile and narrow only its input data. */
|
|
187
|
+
async resolveSelectedLocalAdaptiveInputs(channel: EvolutionChannel, attemptId: string,
|
|
188
|
+
inventory: string, constraints: string): Promise<{
|
|
189
|
+
versionId: string; inventory: string; constraints: string;
|
|
190
|
+
}> {
|
|
191
|
+
const loaded = await this.loadSelected(LOCAL_ADAPTIVE_EXTENSION_KEY, channel);
|
|
192
|
+
const profile = parseLocalAdaptiveInputProfile(loaded.artifact);
|
|
193
|
+
assertLocalAdaptiveInputs(profile, inventory, constraints);
|
|
194
|
+
const binding = await this.registry.bindAttempt(attemptId, LOCAL_ADAPTIVE_EXTENSION_KEY, channel);
|
|
195
|
+
if (binding.versionId !== loaded.versionId) {
|
|
196
|
+
throw new AgentFabricError("AF_CONFLICT", "Selected profile changed before attempt binding");
|
|
197
|
+
}
|
|
198
|
+
return { versionId: binding.versionId, inventory, constraints };
|
|
199
|
+
}
|
|
200
|
+
}
|