@intentius/chant 0.124.13 → 0.124.15
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/bin/chant +11 -0
- package/dist/agents/checks.js +326 -0
- package/dist/agents/discover.js +885 -0
- package/dist/agents/importer.js +17 -0
- package/dist/agents/index.js +12 -0
- package/dist/agents/types.js +19 -0
- package/dist/apply.js +151 -0
- package/dist/attrref.js +70 -0
- package/dist/audit/catalog.js +350 -0
- package/dist/audit/core.js +252 -0
- package/dist/audit/discover.js +633 -0
- package/dist/audit/fetch.js +666 -0
- package/dist/audit/lineage.js +91 -0
- package/dist/audit/nginx.js +366 -0
- package/dist/audit/prior-art.js +132 -0
- package/dist/audit/proof.js +321 -0
- package/dist/audit/report-html.js +194 -0
- package/dist/audit/report-model.js +198 -0
- package/dist/audit/report.js +102 -0
- package/dist/audit/rules-doc.js +144 -0
- package/dist/audit/secrets.js +405 -0
- package/dist/audit/terraform-state.js +140 -0
- package/dist/audit/wrangler.js +262 -0
- package/dist/behaviour-delta.js +401 -0
- package/dist/behaviour-engine.js +357 -0
- package/dist/behaviour-graph.js +83 -0
- package/dist/behaviour-http.js +188 -0
- package/dist/behaviour-kinds.js +172 -0
- package/dist/behaviour-overlay.js +50 -0
- package/dist/behaviour-predict.js +188 -0
- package/dist/behaviour-request.js +194 -0
- package/dist/behaviour.js +1325 -0
- package/dist/build-params.js +108 -0
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +669 -0
- package/dist/builder.js +44 -0
- package/dist/cdk/advise.js +30 -0
- package/dist/cdk/assembly.js +122 -0
- package/dist/cdk/graph.js +444 -0
- package/dist/cdk/tier-map.js +64 -0
- package/dist/cdk/types.js +24 -0
- package/dist/change-set.d.ts +24 -2
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +361 -0
- package/dist/child-project.js +21 -0
- package/dist/ci-jobs.d.ts +227 -0
- package/dist/ci-jobs.d.ts.map +1 -0
- package/dist/ci-jobs.js +448 -0
- package/dist/claimed-fields.js +135 -0
- package/dist/cli/build-options.js +70 -0
- package/dist/cli/build-params-cli.js +139 -0
- package/dist/cli/command-group.js +123 -0
- package/dist/cli/commands/audit-agents.js +326 -0
- package/dist/cli/commands/audit.js +571 -0
- package/dist/cli/commands/build.js +920 -0
- package/dist/cli/commands/carve-apply.js +153 -0
- package/dist/cli/commands/carve-bridge.js +201 -0
- package/dist/cli/commands/carve-emit.js +332 -0
- package/dist/cli/commands/carve-status.js +119 -0
- package/dist/cli/commands/carve.js +204 -0
- package/dist/cli/commands/check-lexicon-docs.js +79 -0
- package/dist/cli/commands/check-lexicon-examples.js +177 -0
- package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
- package/dist/cli/commands/check-lexicon-mcp.js +82 -0
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +104 -0
- package/dist/cli/commands/check-lexicon.js +766 -0
- package/dist/cli/commands/describe.js +85 -0
- package/dist/cli/commands/diff.js +175 -0
- package/dist/cli/commands/doctor.js +350 -0
- package/dist/cli/commands/import-agents.js +155 -0
- package/dist/cli/commands/import.js +670 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
- package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
- package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
- package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
- package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
- package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
- package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
- package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
- package/dist/cli/commands/init-lexicon.js +172 -0
- package/dist/cli/commands/init.js +381 -0
- package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
- package/dist/cli/commands/lexicon-surface-diff.js +145 -0
- package/dist/cli/commands/licenses.js +125 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +948 -0
- package/dist/cli/commands/list.js +63 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +423 -0
- package/dist/cli/commands/onboard.js +403 -0
- package/dist/cli/commands/pinned-upgrade.js +191 -0
- package/dist/cli/commands/update.js +194 -0
- package/dist/cli/commands/vendor.js +217 -0
- package/dist/cli/conflict-check.js +104 -0
- package/dist/cli/debug.js +8 -0
- package/dist/cli/format.js +104 -0
- package/dist/cli/handlers/build.js +267 -0
- package/dist/cli/handlers/carve-apply.js +30 -0
- package/dist/cli/handlers/carve-bridge.js +28 -0
- package/dist/cli/handlers/carve-emit.js +50 -0
- package/dist/cli/handlers/carve-status.js +26 -0
- package/dist/cli/handlers/carve.js +40 -0
- package/dist/cli/handlers/change-set.js +46 -0
- package/dist/cli/handlers/components.js +730 -0
- package/dist/cli/handlers/dev.js +113 -0
- package/dist/cli/handlers/emulator.js +233 -0
- package/dist/cli/handlers/explain.js +54 -0
- package/dist/cli/handlers/fan-out.js +422 -0
- package/dist/cli/handlers/gate-resume.js +275 -0
- package/dist/cli/handlers/generate.js +84 -0
- package/dist/cli/handlers/graph.js +969 -0
- package/dist/cli/handlers/init.js +102 -0
- package/dist/cli/handlers/lifecycle.js +1727 -0
- package/dist/cli/handlers/lint.js +62 -0
- package/dist/cli/handlers/migrate.js +48 -0
- package/dist/cli/handlers/misc.js +391 -0
- package/dist/cli/handlers/operator.js +1162 -0
- package/dist/cli/handlers/override-approve.js +102 -0
- package/dist/cli/handlers/pr.js +503 -0
- package/dist/cli/handlers/promote.js +342 -0
- package/dist/cli/handlers/run-generate.js +206 -0
- package/dist/cli/handlers/run-wave.js +66 -0
- package/dist/cli/handlers/run.js +1191 -0
- package/dist/cli/handlers/scenario.js +325 -0
- package/dist/cli/handlers/search.js +924 -0
- package/dist/cli/handlers/serve.js +32 -0
- package/dist/cli/handlers/vendor.js +154 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/is-entry-point.js +23 -0
- package/dist/cli/lsp/capabilities.js +28 -0
- package/dist/cli/lsp/diagnostics.js +34 -0
- package/dist/cli/lsp/server.js +329 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +2540 -0
- package/dist/cli/mcp/lifecycle-tools.js +80 -0
- package/dist/cli/mcp/op-tools.js +306 -0
- package/dist/cli/mcp/resource-handlers.js +258 -0
- package/dist/cli/mcp/resources/context.js +85 -0
- package/dist/cli/mcp/server.js +353 -0
- package/dist/cli/mcp/tools/build.js +111 -0
- package/dist/cli/mcp/tools/composites.js +95 -0
- package/dist/cli/mcp/tools/explain.js +166 -0
- package/dist/cli/mcp/tools/import.js +49 -0
- package/dist/cli/mcp/tools/lint.js +41 -0
- package/dist/cli/mcp/tools/scaffold.js +111 -0
- package/dist/cli/mcp/tools/search.js +112 -0
- package/dist/cli/mcp/types.js +1 -0
- package/dist/cli/mcp/workspace-plugins.js +91 -0
- package/dist/cli/mcp/workspace-tools.d.ts +1 -1
- package/dist/cli/mcp/workspace-tools.js +564 -0
- package/dist/cli/mcp-config.js +70 -0
- package/dist/cli/plugins.js +313 -0
- package/dist/cli/registry.js +23 -0
- package/dist/cli/reporters/stylish.js +329 -0
- package/dist/cli/version.js +15 -0
- package/dist/cli/watch.js +79 -0
- package/dist/codegen/bound-property-types.js +82 -0
- package/dist/codegen/coverage.js +119 -0
- package/dist/codegen/docs-file-markers.js +60 -0
- package/dist/codegen/docs-pages.js +115 -0
- package/dist/codegen/docs-rule-scanning.js +164 -0
- package/dist/codegen/docs-rules.js +76 -0
- package/dist/codegen/docs-sections.js +117 -0
- package/dist/codegen/docs-sidebar.js +65 -0
- package/dist/codegen/docs-types.js +4 -0
- package/dist/codegen/docs.js +356 -0
- package/dist/codegen/fetch.js +340 -0
- package/dist/codegen/generate-post-synth-barrel.js +63 -0
- package/dist/codegen/generate-registry.js +71 -0
- package/dist/codegen/generate-runtime-index.js +56 -0
- package/dist/codegen/generate-typescript.js +119 -0
- package/dist/codegen/generate.js +115 -0
- package/dist/codegen/json-patch.js +141 -0
- package/dist/codegen/json-schema-to-ts.js +344 -0
- package/dist/codegen/json-schema.js +246 -0
- package/dist/codegen/lexicon-regen.js +412 -0
- package/dist/codegen/naming.js +217 -0
- package/dist/codegen/okf-lexicon.js +245 -0
- package/dist/codegen/package.js +150 -0
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.js +494 -0
- package/dist/codegen/registry.js +60 -0
- package/dist/codegen/rehype-base-url.mjs +68 -0
- package/dist/codegen/rolling-upgrade.js +217 -0
- package/dist/codegen/surface-snapshot.js +537 -0
- package/dist/codegen/testdata/postgres-tags.json +242 -0
- package/dist/codegen/topo-sort.js +36 -0
- package/dist/codegen/typecheck.js +115 -0
- package/dist/codegen/validate.js +200 -0
- package/dist/codegen/version-bump.js +35 -0
- package/dist/components/auto-release.js +143 -0
- package/dist/components/builders.js +47 -0
- package/dist/components/capability-plugin-loader.js +175 -0
- package/dist/components/capability-plugin-schema.js +46 -0
- package/dist/components/capability-plugin.js +99 -0
- package/dist/components/capability.js +60 -0
- package/dist/components/changed-files.js +72 -0
- package/dist/components/cli-support.js +427 -0
- package/dist/components/component.js +120 -0
- package/dist/components/component.schema.json +271 -0
- package/dist/components/config-defaults.js +130 -0
- package/dist/components/deploy-units.js +60 -0
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/discover.js +189 -0
- package/dist/components/driver-output.js +86 -0
- package/dist/components/driver.js +728 -0
- package/dist/components/fan-out-output.js +165 -0
- package/dist/components/fan-out-record.js +94 -0
- package/dist/components/fan-out-run.js +373 -0
- package/dist/components/fan-out-support.js +60 -0
- package/dist/components/fan-out.js +273 -0
- package/dist/components/gate-plan.js +41 -0
- package/dist/components/gated-wave-pipeline.js +46 -0
- package/dist/components/index.js +24 -0
- package/dist/components/layers.js +28 -0
- package/dist/components/manifest-persistence.js +108 -0
- package/dist/components/pilots/alb-ecs.pilot.js +59 -0
- package/dist/components/pilots/dynamodb.pilot.js +51 -0
- package/dist/components/pilots/jar-emr.pilot.js +65 -0
- package/dist/components/pilots/lambda.pilot.js +52 -0
- package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
- package/dist/components/pilots/project.js +12 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +135 -0
- package/dist/components/pr-run.js +363 -0
- package/dist/components/presets/ecs-fargate.js +72 -0
- package/dist/components/presets/index.js +28 -0
- package/dist/components/presets/lambda.js +34 -0
- package/dist/components/presets/single-host-compose.js +41 -0
- package/dist/components/promote.js +549 -0
- package/dist/components/registry.js +34 -0
- package/dist/components/run-progress.js +33 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/driver.js +93 -0
- package/dist/components/sandbox/run.js +115 -0
- package/dist/components/starter-plugin.js +104 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
- package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
- package/dist/components/verbs/bom-writer.js +287 -0
- package/dist/components/verbs/build-archive.js +188 -0
- package/dist/components/verbs/build.js +142 -0
- package/dist/components/verbs/cloud-executor.js +110 -0
- package/dist/components/verbs/component-bom.js +125 -0
- package/dist/components/verbs/ensure-secret.js +50 -0
- package/dist/components/verbs/index.js +61 -0
- package/dist/components/verbs/license-policy.js +75 -0
- package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
- package/dist/components/verbs/process-runner.js +78 -0
- package/dist/components/verbs/r2-sync.js +93 -0
- package/dist/components/verbs/reproducibility.js +62 -0
- package/dist/components/verbs/run-agent.js +577 -0
- package/dist/components/verbs/sbom-generator.js +119 -0
- package/dist/components/verbs/sbom.js +97 -0
- package/dist/components/verbs/shell.js +32 -0
- package/dist/components/verbs/sign.js +221 -0
- package/dist/components/verbs/stub.js +28 -0
- package/dist/components/verbs/tool-sbom-generator.js +154 -0
- package/dist/components/verbs/verify.js +112 -0
- package/dist/components/verbs/vex.js +131 -0
- package/dist/components/verbs/vuln-gate.js +152 -0
- package/dist/components/verbs/vuln-scan.js +288 -0
- package/dist/components/verbs/wait-verify.js +106 -0
- package/dist/components/verbs/wrangler.js +179 -0
- package/dist/components/wave-plan.js +102 -0
- package/dist/composite.d.ts +10 -2
- package/dist/composite.d.ts.map +1 -1
- package/dist/composite.js +313 -0
- package/dist/config-import.d.ts.map +1 -1
- package/dist/config-import.js +22 -0
- package/dist/config-sandbox.js +122 -0
- package/dist/config-static.js +206 -0
- package/dist/config.js +605 -0
- package/dist/content-digest.js +23 -0
- package/dist/declarable.js +114 -0
- package/dist/deep-observation.js +437 -0
- package/dist/detectLexicon.js +54 -0
- package/dist/discovery/cache.js +70 -0
- package/dist/discovery/collect.d.ts +2 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/collect.js +223 -0
- package/dist/discovery/cycles.js +95 -0
- package/dist/discovery/entity-wire-codec.js +391 -0
- package/dist/discovery/entity-wire.js +42 -0
- package/dist/discovery/files.js +121 -0
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +4073 -0
- package/dist/discovery/fold-rank.js +292 -0
- package/dist/discovery/graph.js +115 -0
- package/dist/discovery/host-composite.d.ts +35 -0
- package/dist/discovery/host-composite.d.ts.map +1 -0
- package/dist/discovery/host-composite.js +93 -0
- package/dist/discovery/import.d.ts.map +1 -1
- package/dist/discovery/import.js +65 -0
- package/dist/discovery/index.d.ts +9 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +333 -0
- package/dist/discovery/param-deps.js +487 -0
- package/dist/discovery/resolve.js +39 -0
- package/dist/discovery/sandbox/bundle.js +214 -0
- package/dist/discovery/sandbox/child-errors.js +26 -0
- package/dist/discovery/sandbox/config-run.js +100 -0
- package/dist/discovery/sandbox/config-wire.js +183 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.js +333 -0
- package/dist/discovery/sandbox/fork.js +229 -0
- package/dist/discovery/sandbox/policy-run.js +142 -0
- package/dist/discovery/sandbox/policy-wire.js +152 -0
- package/dist/discovery/sandbox/run.js +77 -0
- package/dist/discovery/specifier-resolve.js +320 -0
- package/dist/discovery/walk.js +284 -0
- package/dist/effect-receipt.js +340 -0
- package/dist/env.js +55 -0
- package/dist/errors.js +62 -0
- package/dist/fold/fold.js +1434 -0
- package/dist/fold/foldable-helpers.js +229 -0
- package/dist/fold/subset.js +696 -0
- package/dist/fold-provenance.js +414 -0
- package/dist/gate-decision.d.ts +20 -9
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +313 -0
- package/dist/gate-resume-run.js +154 -0
- package/dist/gated-waves.js +165 -0
- package/dist/governance.js +43 -0
- package/dist/graph-declared.js +45 -0
- package/dist/graph-detail.js +207 -0
- package/dist/graph-dot.js +54 -0
- package/dist/graph-effective.js +107 -0
- package/dist/graph-ir.js +623 -0
- package/dist/graph-layout.js +184 -0
- package/dist/graph-lens.js +105 -0
- package/dist/graph-mermaid.js +83 -0
- package/dist/graph-ops.js +39 -0
- package/dist/graph-refs.js +156 -0
- package/dist/held-elsewhere.js +87 -0
- package/dist/identity.js +251 -0
- package/dist/import/base-parser.js +34 -0
- package/dist/import/embedded.js +255 -0
- package/dist/import/generator.js +1 -0
- package/dist/import/ir-utils.js +85 -0
- package/dist/import/parser-options.js +45 -0
- package/dist/import/parser.js +1 -0
- package/dist/index.js +118 -0
- package/dist/intrinsic-interpolation.js +73 -0
- package/dist/intrinsic.js +31 -0
- package/dist/join-key.js +35 -0
- package/dist/kubectl-context.js +150 -0
- package/dist/lexicon-config.js +58 -0
- package/dist/lexicon-integrity.js +47 -0
- package/dist/lexicon-manifest.js +70 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +241 -0
- package/dist/lexicon-output.js +218 -0
- package/dist/lexicon-plugin-helpers.js +104 -0
- package/dist/lexicon-schema.js +136 -0
- package/dist/lexicon.js +130 -0
- package/dist/lifecycle/affected.js +224 -0
- package/dist/lifecycle/assert-live.js +110 -0
- package/dist/lifecycle/build-ledger-store.js +160 -0
- package/dist/lifecycle/build-ledger.js +155 -0
- package/dist/lifecycle/change-set.js +356 -0
- package/dist/lifecycle/converge-ledger.js +161 -0
- package/dist/lifecycle/deep-diff.js +233 -0
- package/dist/lifecycle/deep-observe.js +147 -0
- package/dist/lifecycle/digest.js +143 -0
- package/dist/lifecycle/disruption.js +135 -0
- package/dist/lifecycle/gate-applied.js +113 -0
- package/dist/lifecycle/gate-audit.d.ts +69 -2
- package/dist/lifecycle/gate-audit.d.ts.map +1 -1
- package/dist/lifecycle/gate-audit.js +415 -0
- package/dist/lifecycle/gate-ledger-select.js +89 -0
- package/dist/lifecycle/gate-ledger.d.ts +24 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +201 -0
- package/dist/lifecycle/gate-origin.js +117 -0
- package/dist/lifecycle/gate-pending.js +58 -0
- package/dist/lifecycle/git.js +1182 -0
- package/dist/lifecycle/identity.js +58 -0
- package/dist/lifecycle/index.js +26 -0
- package/dist/lifecycle/lease.js +283 -0
- package/dist/lifecycle/ledger-port.js +1 -0
- package/dist/lifecycle/legacy-digest.js +91 -0
- package/dist/lifecycle/live-diff.js +274 -0
- package/dist/lifecycle/local-note.js +15 -0
- package/dist/lifecycle/member-ledger.js +122 -0
- package/dist/lifecycle/observation-baseline.js +155 -0
- package/dist/lifecycle/observe.js +375 -0
- package/dist/lifecycle/oras-referrer-lookup.js +82 -0
- package/dist/lifecycle/override-ledger.js +54 -0
- package/dist/lifecycle/plan-digest.js +110 -0
- package/dist/lifecycle/plan-ledger.js +129 -0
- package/dist/lifecycle/receipt-plan.js +192 -0
- package/dist/lifecycle/release-ledger.js +155 -0
- package/dist/lifecycle/release-read.js +64 -0
- package/dist/lifecycle/replay.js +193 -0
- package/dist/lifecycle/rollback.js +102 -0
- package/dist/lifecycle/run-ledger.js +183 -0
- package/dist/lifecycle/scenario-eval.js +204 -0
- package/dist/lifecycle/scenario.js +214 -0
- package/dist/lifecycle/snapshot.js +239 -0
- package/dist/lifecycle/status.js +447 -0
- package/dist/lifecycle/symptoms.js +117 -0
- package/dist/lifecycle/teardown.js +255 -0
- package/dist/lifecycle/types.js +1 -0
- package/dist/lifecycle/unobserved-gate.js +33 -0
- package/dist/lifecycle/wave-plans.js +68 -0
- package/dist/lifecycle/work-item-id.js +36 -0
- package/dist/lifecycle/work-lease.js +393 -0
- package/dist/lint/component-checks.js +87 -0
- package/dist/lint/config.js +483 -0
- package/dist/lint/declarative.js +122 -0
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +138 -0
- package/dist/lint/engine.js +246 -0
- package/dist/lint/knowledge-checks.js +64 -0
- package/dist/lint/named-checks.js +22 -0
- package/dist/lint/output-checks.js +87 -0
- package/dist/lint/output-docs.js +163 -0
- package/dist/lint/parser.js +33 -0
- package/dist/lint/pipeline-change-gate.js +70 -0
- package/dist/lint/policy-import.d.ts +1 -1
- package/dist/lint/policy-import.d.ts.map +1 -1
- package/dist/lint/policy-import.js +62 -0
- package/dist/lint/policy-sandbox.js +86 -0
- package/dist/lint/policy.js +122 -0
- package/dist/lint/post-synth.js +87 -0
- package/dist/lint/presets/relaxed.json +26 -0
- package/dist/lint/presets/strict.json +24 -24
- package/dist/lint/receipt-checks.js +83 -0
- package/dist/lint/rule-loader.d.ts.map +1 -1
- package/dist/lint/rule-loader.js +89 -0
- package/dist/lint/rule-registry.js +86 -0
- package/dist/lint/rule.js +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
- package/dist/lint/rules/comp/index.js +33 -0
- package/dist/lint/rules/comp/support.js +129 -0
- package/dist/lint/rules/composite-scope.js +51 -0
- package/dist/lint/rules/cor017-composite-name-match.js +93 -0
- package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
- package/dist/lint/rules/cor021-env-literal-name.js +100 -0
- package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
- package/dist/lint/rules/declarable-naming-convention.js +62 -0
- package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
- package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
- package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
- package/dist/lint/rules/evl004-spread-non-const.js +94 -0
- package/dist/lint/rules/evl005-resource-block-body.js +41 -0
- package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
- package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
- package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
- package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
- package/dist/lint/rules/export-required.js +115 -0
- package/dist/lint/rules/file-declarable-limit.js +88 -0
- package/dist/lint/rules/flat-declarations.js +62 -0
- package/dist/lint/rules/index.js +90 -0
- package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
- package/dist/lint/rules/no-redundant-type-import.js +76 -0
- package/dist/lint/rules/no-redundant-value-cast.js +41 -0
- package/dist/lint/rules/no-string-ref.js +59 -0
- package/dist/lint/rules/no-unused-declarable-import.js +75 -0
- package/dist/lint/rules/no-unused-declarable.js +142 -0
- package/dist/lint/rules/op/index.js +28 -0
- package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
- package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
- package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
- package/dist/lint/rules/op/support.js +11 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
- package/dist/lint/rules/property-kind.js +56 -0
- package/dist/lint/rules/single-concern-file.js +71 -0
- package/dist/lint/rules/sys001-no-literal-key.js +60 -0
- package/dist/lint/selectors.js +168 -0
- package/dist/lint/suppressions.js +129 -0
- package/dist/lint/typescript.js +17 -0
- package/dist/live-endpoint.js +113 -0
- package/dist/live-read-session.js +76 -0
- package/dist/lsp/lexicon-providers.js +163 -0
- package/dist/lsp/types.js +5 -0
- package/dist/managed-fields.js +293 -0
- package/dist/mcp/types.js +4 -0
- package/dist/notify-verify.js +0 -1
- package/dist/notify.js +0 -1
- package/dist/notify.schema.json +72 -0
- package/dist/observation.js +266 -0
- package/dist/okf-read.js +134 -0
- package/dist/okf.js +275 -0
- package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +313 -0
- package/dist/op/activities/apply.js +582 -0
- package/dist/op/activities/build.js +18 -0
- package/dist/op/activities/change-coverage.js +40 -0
- package/dist/op/activities/change-set.d.ts +8 -3
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +67 -0
- package/dist/op/activities/converge.js +492 -0
- package/dist/op/activities/decide.js +211 -0
- package/dist/op/activities/env-teardown.js +110 -0
- package/dist/op/activities/factory.js +591 -0
- package/dist/op/activities/forge-pull-request.js +82 -0
- package/dist/op/activities/gh-host.js +78 -0
- package/dist/op/activities/guard-validate.js +165 -0
- package/dist/op/activities/http-check.js +40 -0
- package/dist/op/activities/index.js +59 -0
- package/dist/op/activities/lexicon-upgrade.js +629 -0
- package/dist/op/activities/lifecycle.js +109 -0
- package/dist/op/activities/pipeline-audit.js +147 -0
- package/dist/op/activities/policy.js +26 -0
- package/dist/op/activities/predict-behaviour.js +341 -0
- package/dist/op/activities/propose-upgrade.js +165 -0
- package/dist/op/activities/reconcile.d.ts +28 -16
- package/dist/op/activities/reconcile.d.ts.map +1 -1
- package/dist/op/activities/reconcile.js +1011 -0
- package/dist/op/activities/shell.js +118 -0
- package/dist/op/activities/source-release.js +97 -0
- package/dist/op/activities/source-rollback.js +165 -0
- package/dist/op/activities/teardown.js +17 -0
- package/dist/op/activities/util.js +6 -0
- package/dist/op/activities/wait.js +51 -0
- package/dist/op/activities/work-evidence.js +35 -0
- package/dist/op/activities/workflow-audit.js +133 -0
- package/dist/op/activity-contract-registry.js +106 -0
- package/dist/op/activity-contract.js +232 -0
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/activity-profiles.js +129 -0
- package/dist/op/activity-registry.js +105 -0
- package/dist/op/activity-runtime.js +28 -0
- package/dist/op/builders.d.ts +1 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +801 -0
- package/dist/op/change-signal.js +118 -0
- package/dist/op/composites/apply-op.js +151 -0
- package/dist/op/composites/behaviour-op.js +65 -0
- package/dist/op/composites/converge-op.js +141 -0
- package/dist/op/composites/index.js +19 -0
- package/dist/op/composites/lexicon-upgrade-op.js +107 -0
- package/dist/op/composites/pipeline-audit-op.js +50 -0
- package/dist/op/composites/reconcile-op.js +84 -0
- package/dist/op/composites/watch-op.js +69 -0
- package/dist/op/composites/workflow-audit-op.js +57 -0
- package/dist/op/converge-rule.js +205 -0
- package/dist/op/cron.js +146 -0
- package/dist/op/decide-backend.js +181 -0
- package/dist/op/decide-config.js +68 -0
- package/dist/op/decide-read-inputs.js +141 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +213 -0
- package/dist/op/duration.js +21 -0
- package/dist/op/emulator-freshness.js +80 -0
- package/dist/op/emulator-lifecycle.js +269 -0
- package/dist/op/factory.js +83 -0
- package/dist/op/gate-approval.js +172 -0
- package/dist/op/gate-name.js +86 -0
- package/dist/op/gate-plan-context.js +167 -0
- package/dist/op/gate-point-run.js +67 -0
- package/dist/op/gate-point.js +111 -0
- package/dist/op/gate-resume-plan.js +155 -0
- package/dist/op/gate-resume.js +31 -0
- package/dist/op/gate-review.js +290 -0
- package/dist/op/gate-run-locator.js +85 -0
- package/dist/op/gate-summary.js +100 -0
- package/dist/op/gate-wait.js +56 -0
- package/dist/op/gate.js +449 -0
- package/dist/op/generate-pipeline.js +124 -0
- package/dist/op/index.js +51 -0
- package/dist/op/lifecycle-receipt-store.js +45 -0
- package/dist/op/local-executor.js +1164 -0
- package/dist/op/local-output.js +140 -0
- package/dist/op/op-ir.js +317 -0
- package/dist/op/op-verb-class.js +131 -0
- package/dist/op/op-waves-notify.js +136 -0
- package/dist/op/op-waves-run.d.ts +1 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +1657 -0
- package/dist/op/op-waves.js +409 -0
- package/dist/op/operator.js +743 -0
- package/dist/op/receipt-store.js +94 -0
- package/dist/op/resource.js +23 -0
- package/dist/op/run-context.js +28 -0
- package/dist/op/run-live.js +256 -0
- package/dist/op/runtime.js +21 -0
- package/dist/op/runtimes/local.js +195 -0
- package/dist/op/source-archive.js +131 -0
- package/dist/op/step-output-ref.js +397 -0
- package/dist/op/steward-beside.js +169 -0
- package/dist/op/steward-points.js +138 -0
- package/dist/op/steward-turn.js +59 -0
- package/dist/op/steward.js +317 -0
- package/dist/op/types.js +25 -0
- package/dist/op/work-lease-decl.js +81 -0
- package/dist/op/work-lease-run.js +393 -0
- package/dist/override-gate.js +284 -0
- package/dist/owner-chain.js +74 -0
- package/dist/ownership.js +92 -0
- package/dist/params.js +61 -0
- package/dist/plan-summary.js +711 -0
- package/dist/point-decision.js +211 -0
- package/dist/pr-forge.js +810 -0
- package/dist/pr-loop.js +203 -0
- package/dist/project-codegen.js +191 -0
- package/dist/project-root.js +74 -0
- package/dist/project-validation.js +50 -0
- package/dist/provenance.js +157 -0
- package/dist/pseudo-parameter.js +40 -0
- package/dist/reconcile.js +450 -0
- package/dist/resource-attributes.js +33 -0
- package/dist/runtime-adapter.js +77 -0
- package/dist/runtime.js +66 -0
- package/dist/secret-materialization.js +153 -0
- package/dist/secret-provenance.js +250 -0
- package/dist/serializer-walker.js +112 -0
- package/dist/serializer.js +1 -0
- package/dist/sort.js +45 -0
- package/dist/stack-output.js +115 -0
- package/dist/telemetry-attribution.js +149 -0
- package/dist/terraform/__fixtures__/build-graph.js +63 -0
- package/dist/terraform/adopt-state.js +40 -0
- package/dist/terraform/aws-resources.js +384 -0
- package/dist/terraform/bridge.js +204 -0
- package/dist/terraform/carve-provider.js +155 -0
- package/dist/terraform/carve.js +138 -0
- package/dist/terraform/data-source-shape.js +57 -0
- package/dist/terraform/emit-source.js +36 -0
- package/dist/terraform/excise.js +123 -0
- package/dist/terraform/graduate.js +128 -0
- package/dist/terraform/graph.js +435 -0
- package/dist/terraform/hcl2json.js +60 -0
- package/dist/terraform/manifest.js +149 -0
- package/dist/terraform/parse.js +81 -0
- package/dist/terraform/providers/aws.js +146 -0
- package/dist/terraform/providers/gcp.js +294 -0
- package/dist/terraform/providers/index.js +17 -0
- package/dist/terraform/providers/kubernetes.js +253 -0
- package/dist/terraform/score.js +151 -0
- package/dist/terraform/state.js +67 -0
- package/dist/terraform/tier-map.js +74 -0
- package/dist/terraform/types.js +13 -0
- package/dist/terraform/unified-diff.js +132 -0
- package/dist/testing.js +295 -0
- package/dist/toml-emit.js +153 -0
- package/dist/toml-parse.js +355 -0
- package/dist/toml-utils.js +60 -0
- package/dist/toml.js +428 -0
- package/dist/ts-loader.d.ts +46 -0
- package/dist/ts-loader.d.ts.map +1 -0
- package/dist/ts-loader.js +103 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +61 -0
- package/dist/validation.js +20 -0
- package/dist/vendor/js-yaml-load.js +1662 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.js +119 -0
- package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
- package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
- package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/sessions.js +91 -0
- package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
- package/dist/workspace/agent-cli.js +99 -0
- package/dist/workspace/agent.schema.json +390 -0
- package/dist/workspace/answers-ledger.js +89 -0
- package/dist/workspace/box-cli.js +142 -0
- package/dist/workspace/box-factory-write.js +196 -0
- package/dist/workspace/box-factory-write.schema.json +342 -0
- package/dist/workspace/box-factory.js +88 -0
- package/dist/workspace/box-intent.js +126 -0
- package/dist/workspace/box-isolation.js +169 -0
- package/dist/workspace/box-listing-write.schema.json +204 -0
- package/dist/workspace/box-listing.js +298 -0
- package/dist/workspace/box-publish.js +352 -0
- package/dist/workspace/box-publish.schema.json +498 -0
- package/dist/workspace/box-services.js +39 -0
- package/dist/workspace/broker-protocol.js +359 -0
- package/dist/workspace/broker-protocol.schema.json +228 -0
- package/dist/workspace/change-set.schema.json +462 -0
- package/dist/workspace/changes-cli.js +56 -0
- package/dist/workspace/changes.js +446 -0
- package/dist/workspace/changes.schema.json +642 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
- package/dist/workspace/chant-migrations.js +113 -0
- package/dist/workspace/check.schema.json +433 -0
- package/dist/workspace/checks/box-isolation.js +62 -0
- package/dist/workspace/checks/boxes.js +491 -0
- package/dist/workspace/checks/diagrams.js +104 -0
- package/dist/workspace/checks/generated.js +419 -0
- package/dist/workspace/checks/ledgers.js +212 -0
- package/dist/workspace/checks/links.js +191 -0
- package/dist/workspace/checks/live.js +100 -0
- package/dist/workspace/checks/pipelines.js +188 -0
- package/dist/workspace/checks/records.js +169 -0
- package/dist/workspace/checks.js +426 -0
- package/dist/workspace/ci-green-cli.js +125 -0
- package/dist/workspace/ci-green-forge.js +83 -0
- package/dist/workspace/ci-green-workflow.d.ts +3 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +202 -0
- package/dist/workspace/ci-green.js +325 -0
- package/dist/workspace/ci-last-green.schema.json +158 -0
- package/dist/workspace/class-data.d.ts +84 -0
- package/dist/workspace/class-data.d.ts.map +1 -0
- package/dist/workspace/class-data.js +238 -0
- package/dist/workspace/compose-graph.js +235 -0
- package/dist/workspace/compose-reports.js +146 -0
- package/dist/workspace/composites.js +264 -0
- package/dist/workspace/composites.schema.json +617 -0
- package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
- package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
- package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
- package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
- package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
- package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
- package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
- package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
- package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
- package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
- package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
- package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
- package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
- package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
- package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
- package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
- package/dist/workspace/conformance/broker.js +736 -0
- package/dist/workspace/conformance/index.js +673 -0
- package/dist/workspace/conformance/vitest.js +108 -0
- package/dist/workspace/conformance/writer.js +1080 -0
- package/dist/workspace/decide.js +879 -0
- package/dist/workspace/decision-points.schema.json +135 -135
- package/dist/workspace/declaration-write-cli.js +74 -0
- package/dist/workspace/declaration-write.js +238 -0
- package/dist/workspace/declaration.js +938 -0
- package/dist/workspace/declaration.schema.json +1413 -1413
- package/dist/workspace/declared-kinds.js +106 -0
- package/dist/workspace/environments.js +164 -0
- package/dist/workspace/evidence.schema.json +279 -0
- package/dist/workspace/export-cli.js +185 -0
- package/dist/workspace/export.js +499 -0
- package/dist/workspace/factory-rules.js +175 -0
- package/dist/workspace/generated-files.js +92 -0
- package/dist/workspace/glob.js +45 -0
- package/dist/workspace/graph-cache.js +352 -0
- package/dist/workspace/graph-cli.js +348 -0
- package/dist/workspace/graph.schema.json +1338 -0
- package/dist/workspace/identity-gate-rule.js +75 -0
- package/dist/workspace/identity.js +126 -0
- package/dist/workspace/import.js +283 -0
- package/dist/workspace/init.js +620 -0
- package/dist/workspace/intent-cli.js +263 -0
- package/dist/workspace/intent-joins.js +201 -0
- package/dist/workspace/intent-record.js +352 -0
- package/dist/workspace/intent-record.schema.json +864 -0
- package/dist/workspace/intent.js +1582 -0
- package/dist/workspace/intent.schema.json +2420 -0
- package/dist/workspace/json-edit.js +221 -0
- package/dist/workspace/jsonc.js +235 -0
- package/dist/workspace/kind-readers.js +103 -0
- package/dist/workspace/kinds.d.ts +3 -7
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +553 -0
- package/dist/workspace/lexicon-symbols.js +112 -0
- package/dist/workspace/lineage-adopt-cli.js +102 -0
- package/dist/workspace/lineage-adopt.js +360 -0
- package/dist/workspace/lineage-check.js +282 -0
- package/dist/workspace/lineage-cli.js +121 -0
- package/dist/workspace/lineage-hash-index.js +284 -0
- package/dist/workspace/lineage-init.js +507 -0
- package/dist/workspace/lineage-lock.js +387 -0
- package/dist/workspace/lineage-merge.js +45 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.js +329 -0
- package/dist/workspace/lineage-provenance.js +36 -0
- package/dist/workspace/lineage-update.js +138 -0
- package/dist/workspace/lineage-upgrade-cli.js +131 -0
- package/dist/workspace/lineage-upgrade.js +889 -0
- package/dist/workspace/lineage-vendor.js +171 -0
- package/dist/workspace/lineage-version.js +128 -0
- package/dist/workspace/lineage-versions.js +275 -0
- package/dist/workspace/links.js +330 -0
- package/dist/workspace/ls-generated.js +95 -0
- package/dist/workspace/ls.js +277 -0
- package/dist/workspace/ls.schema.json +333 -0
- package/dist/workspace/member-commands.js +508 -0
- package/dist/workspace/member-handles.js +216 -0
- package/dist/workspace/member-pipeline.js +258 -0
- package/dist/workspace/member-run.js +117 -0
- package/dist/workspace/member-write.schema.json +193 -0
- package/dist/workspace/nested-graph.js +156 -0
- package/dist/workspace/nesting.js +38 -0
- package/dist/workspace/patch.js +301 -0
- package/dist/workspace/patch.schema.json +321 -0
- package/dist/workspace/pin-cli.js +55 -0
- package/dist/workspace/pin-integrity.js +100 -0
- package/dist/workspace/plan-summary.schema.json +155 -0
- package/dist/workspace/point-answer.schema.json +129 -129
- package/dist/workspace/points-cli.js +291 -0
- package/dist/workspace/points-write.schema.json +660 -0
- package/dist/workspace/points.js +521 -0
- package/dist/workspace/points.schema.json +916 -0
- package/dist/workspace/pr-report.schema.json +108 -0
- package/dist/workspace/principal-classes.d.ts +4 -11
- package/dist/workspace/principal-classes.d.ts.map +1 -1
- package/dist/workspace/principal-classes.js +162 -0
- package/dist/workspace/reason-codes.js +326 -0
- package/dist/workspace/record-assets.js +151 -0
- package/dist/workspace/record-decided.js +105 -0
- package/dist/workspace/record-sessions.js +96 -0
- package/dist/workspace/record-source.js +114 -0
- package/dist/workspace/records-amend.schema.json +284 -0
- package/dist/workspace/records-checkout.js +76 -0
- package/dist/workspace/records-cli.js +707 -0
- package/dist/workspace/records-close.js +116 -0
- package/dist/workspace/records-close.schema.json +278 -0
- package/dist/workspace/records-new.schema.json +202 -0
- package/dist/workspace/records-review.schema.json +367 -0
- package/dist/workspace/records-since.js +317 -0
- package/dist/workspace/records-since.schema.json +218 -0
- package/dist/workspace/records-write.js +1229 -0
- package/dist/workspace/records.js +1345 -0
- package/dist/workspace/records.schema.json +604 -0
- package/dist/workspace/reference-kinds/app/package.json +10 -0
- package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/dist/workspace/returns.js +303 -0
- package/dist/workspace/root-refusal.js +75 -0
- package/dist/workspace/run-statement.schema.json +622 -0
- package/dist/workspace/runs-cli.js +584 -0
- package/dist/workspace/runs-write.schema.json +994 -0
- package/dist/workspace/runs.js +708 -0
- package/dist/workspace/runs.schema.json +1257 -0
- package/dist/workspace/runtimes.js +76 -0
- package/dist/workspace/session-kinds.js +83 -0
- package/dist/workspace/signers.schema.json +206 -0
- package/dist/workspace/source-block.js +125 -0
- package/dist/workspace/squash.js +177 -0
- package/dist/workspace/status-gates.js +162 -0
- package/dist/workspace/status-stewards.js +330 -0
- package/dist/workspace/status.js +514 -0
- package/dist/workspace/status.schema.json +998 -0
- package/dist/workspace/steward-index.js +191 -0
- package/dist/workspace/symbols.js +188 -0
- package/dist/workspace/template-manifest.js +227 -0
- package/dist/workspace/template-pins.js +115 -0
- package/dist/workspace/trailer-joins.js +65 -0
- package/dist/workspace/trailers.js +111 -0
- package/dist/workspace/tree.js +136 -0
- package/dist/workspace/trust/attestor.js +63 -0
- package/dist/workspace/trust/dsse.js +108 -0
- package/dist/workspace/trust/evidence-cli.js +175 -0
- package/dist/workspace/trust/evidence.js +161 -0
- package/dist/workspace/trust/gate-seal.js +226 -0
- package/dist/workspace/trust/policy.js +190 -0
- package/dist/workspace/trust/provenance.js +180 -0
- package/dist/workspace/trust/rotation.js +298 -0
- package/dist/workspace/trust/run-commits.js +101 -0
- package/dist/workspace/trust/run-statement.js +182 -0
- package/dist/workspace/trust/seal.js +272 -0
- package/dist/workspace/trust/signers-cli.js +152 -0
- package/dist/workspace/trust/ssh-commit.js +178 -0
- package/dist/workspace/trust/test-repo.js +118 -0
- package/dist/workspace/trust/trust-files.js +193 -0
- package/dist/workspace/trust/verify-cli.js +63 -0
- package/dist/workspace/trust/verify.js +192 -0
- package/dist/workspace/which-chant.js +141 -0
- package/dist/workspace/wip-cli.js +95 -0
- package/dist/workspace/wip-write.schema.json +504 -0
- package/dist/workspace/wip.js +631 -0
- package/dist/workspace/wip.schema.json +334 -0
- package/dist/workspace/work-cli.js +448 -0
- package/dist/workspace/work-evidence.js +122 -0
- package/dist/workspace/work-evidence.schema.json +222 -0
- package/dist/workspace/work-history.schema.json +421 -0
- package/dist/workspace/work-lease.schema.json +246 -0
- package/dist/workspace/work-size.js +77 -0
- package/dist/workspace/work.js +283 -0
- package/dist/workspace/workspace-kinds.schema.json +259 -259
- package/dist/workspace/workspace-principals.schema.json +53 -53
- package/dist/workspace/write-lock-cli.js +105 -0
- package/dist/workspace/write-lock.js +424 -0
- package/dist/workspace/write-lock.schema.json +172 -0
- package/dist/workspace/write-scope.js +484 -0
- package/dist/yaml.js +459 -0
- package/package.json +2 -2
- package/src/build.ts +8 -0
- package/src/change-set.test.ts +48 -0
- package/src/change-set.ts +52 -2
- package/src/ci-jobs.test.ts +49 -0
- package/src/ci-jobs.ts +515 -0
- package/src/cli/commands/check-lexicon-plugin.ts +2 -0
- package/src/cli/commands/lint.ts +3 -0
- package/src/cli/commands/migrate.ts +2 -0
- package/src/cli/compiled-cli.e2e.test.ts +165 -0
- package/src/cli/main.ts +121 -27
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -0
- package/src/components/discover.ts +2 -0
- package/src/components/pr-pipeline.ts +3 -2
- package/src/components/sandbox/driver.ts +3 -1
- package/src/composite.ts +15 -3
- package/src/config-import.ts +3 -0
- package/src/discovery/collect.ts +15 -1
- package/src/discovery/fold-build-parity.test.ts +71 -0
- package/src/discovery/fold-import.ts +25 -127
- package/src/discovery/fold-project-provenance.test.ts +21 -4
- package/src/discovery/host-composite.ts +101 -0
- package/src/discovery/import.ts +2 -0
- package/src/discovery/index.ts +11 -1
- package/src/discovery/sandbox/driver.ts +3 -1
- package/src/gate-decision-bundle.test.ts +5 -4
- package/src/gate-decision-identity.test.ts +59 -6
- package/src/gate-decision.ts +81 -21
- package/src/lexicon-module.ts +3 -0
- package/src/lifecycle/gate-audit.test.ts +55 -1
- package/src/lifecycle/gate-audit.ts +116 -3
- package/src/lifecycle/gate-ledger.test.ts +20 -1
- package/src/lifecycle/gate-ledger.ts +33 -1
- package/src/lint/config.ts +1 -1
- package/src/lint/discover.ts +28 -8
- package/src/lint/policy-import.ts +4 -1
- package/src/lint/rule-loader.ts +2 -0
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +11 -3
- package/src/op/activities/lexicon-upgrade.test.ts +2 -0
- package/src/op/activities/reconcile.test.ts +62 -2
- package/src/op/activities/reconcile.ts +46 -22
- package/src/op/builders.ts +1 -1
- package/src/op/discover.ts +3 -0
- package/src/op/op-waves-run.ts +4 -3
- package/src/op/op-waves.test.ts +2 -0
- package/src/op/work-lease-run.test.ts +12 -4
- package/src/ts-loader.test.ts +23 -0
- package/src/ts-loader.ts +122 -0
- package/src/workspace/__fixtures__/contract-repo.ts +17 -1
- package/src/workspace/__fixtures__/sessions.ts +3 -3
- package/src/workspace/change-set.schema.json +5 -3
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
- package/src/workspace/ci-green-workflow.test.ts +4 -4
- package/src/workspace/ci-green-workflow.ts +5 -2
- package/src/workspace/class-data.test.ts +64 -0
- package/src/workspace/class-data.ts +236 -0
- package/src/workspace/declaration.ts +1 -1
- package/src/workspace/kinds.ts +7 -36
- package/src/workspace/lineage-migrations.ts +2 -0
- package/src/workspace/points.ts +1 -1
- package/src/workspace/principal-classes.ts +12 -95
- package/src/workspace/records-formats.test.ts +7 -2
- package/src/workspace/records-sessions-write.test.ts +3 -3
- package/src/workspace/status-contract.e2e.test.ts +37 -0
- package/src/workspace/status-contract.test.ts +23 -21
|
@@ -0,0 +1,1182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git plumbing operations for the chant/lifecycle orphan branch.
|
|
3
|
+
*
|
|
4
|
+
* All operations use git plumbing commands — no checkout, no branch switching,
|
|
5
|
+
* no working tree changes.
|
|
6
|
+
*/
|
|
7
|
+
import { getRuntime } from "../runtime-adapter.js";
|
|
8
|
+
import { memberLedgerPrefix, resolveMemberLedger } from "./member-ledger.js";
|
|
9
|
+
import { LIFECYCLE_LOCAL_NOTE } from "./local-note.js";
|
|
10
|
+
const STATE_BRANCH = "chant/lifecycle";
|
|
11
|
+
/**
|
|
12
|
+
* The branch path of `dir` for the project at `cwd` (#2538): `dir` itself at
|
|
13
|
+
* level 0 and for the root member `.`, `_members/<member>/<dir>` for any
|
|
14
|
+
* other workspace member. Every store in this module and the ones built on it
|
|
15
|
+
* (releases, snapshots, observation baselines, runs, converge records, gates,
|
|
16
|
+
* build records) goes through here, so none of them names the prefix itself.
|
|
17
|
+
* See ./member-ledger.ts for who counts as a member.
|
|
18
|
+
*/
|
|
19
|
+
export async function ledgerDir(dir, opts) {
|
|
20
|
+
return `${await ledgerPrefixOf(opts)}${dir}`;
|
|
21
|
+
}
|
|
22
|
+
async function ledgerPrefixOf(opts) {
|
|
23
|
+
if (opts?.ledgerPrefix !== undefined)
|
|
24
|
+
return opts.ledgerPrefix;
|
|
25
|
+
if (opts?.member !== undefined) {
|
|
26
|
+
return memberLedgerPrefix((typeof opts.member === "string" ? [opts.member] : [...opts.member]).filter((m) => m !== "."));
|
|
27
|
+
}
|
|
28
|
+
return (await resolveMemberLedger(opts?.cwd ?? process.cwd())).prefix;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The identity chant's ledger commits fall back to when the checkout has none
|
|
32
|
+
* of its own (#2301).
|
|
33
|
+
*
|
|
34
|
+
* A CI checkout is the ordinary case here, not an exotic one: GitLab CI,
|
|
35
|
+
* GitHub Actions and Forgejo Actions all clone without `user.email` or
|
|
36
|
+
* `user.name`, and `git commit-tree` refuses outright rather than inventing
|
|
37
|
+
* one — `fatal: unable to auto-detect email address`. Every write in this
|
|
38
|
+
* module goes through `commit-tree`, so before #2301 a gate's pending fact,
|
|
39
|
+
* a run record, a snapshot and a release record all died the same way the
|
|
40
|
+
* moment they ran anywhere but a developer's laptop.
|
|
41
|
+
*
|
|
42
|
+
* This is a *fallback*, applied per-invocation via `git -c` and field by
|
|
43
|
+
* field: only the half the checkout cannot supply is filled in. A configured
|
|
44
|
+
* `user.name` or `user.email` — a developer's, or a CI job that sets one
|
|
45
|
+
* deliberately — is left exactly as it is and still authors these commits, and
|
|
46
|
+
* nothing is written to the repository's config either way. Override the
|
|
47
|
+
* fallback with `CHANT_LIFECYCLE_COMMITTER_NAME` / `_EMAIL` when a project
|
|
48
|
+
* wants its ledger commits attributed to a bot account it controls.
|
|
49
|
+
*/
|
|
50
|
+
const LEDGER_COMMITTER_NAME = "chant";
|
|
51
|
+
const LEDGER_COMMITTER_EMAIL = "chant@localhost";
|
|
52
|
+
/**
|
|
53
|
+
* `git -c` arguments that give {@link writeBlobToPath}'s `commit-tree` a
|
|
54
|
+
* committer when — and only when — the checkout cannot supply one itself.
|
|
55
|
+
* Empty for any checkout that has an identity, which keeps every existing
|
|
56
|
+
* caller's commits byte-identical to what they were before #2301.
|
|
57
|
+
*/
|
|
58
|
+
async function ledgerCommitIdentityArgs(cwd) {
|
|
59
|
+
const rt = getRuntime();
|
|
60
|
+
const probe = await rt.spawn(["git", "var", "GIT_COMMITTER_IDENT"], { cwd, env: C_LOCALE_ENV });
|
|
61
|
+
if (probe.exitCode === 0)
|
|
62
|
+
return [];
|
|
63
|
+
// Per-field, not all-or-nothing (#2309 review). `git var` fails if *either*
|
|
64
|
+
// half is missing, so filling in both would overwrite a configured
|
|
65
|
+
// `user.name` in the checkout that has a name but no email — which is not
|
|
66
|
+
// what "a configured identity is left exactly as it is" promises.
|
|
67
|
+
const args = [];
|
|
68
|
+
for (const [key, envVar, fallback] of [
|
|
69
|
+
["user.name", "CHANT_LIFECYCLE_COMMITTER_NAME", LEDGER_COMMITTER_NAME],
|
|
70
|
+
["user.email", "CHANT_LIFECYCLE_COMMITTER_EMAIL", LEDGER_COMMITTER_EMAIL],
|
|
71
|
+
]) {
|
|
72
|
+
const configured = await rt.spawn(["git", "config", "--get", key], { cwd, env: C_LOCALE_ENV });
|
|
73
|
+
if (configured.exitCode === 0 && configured.stdout.trim() !== "")
|
|
74
|
+
continue;
|
|
75
|
+
args.push("-c", `${key}=${process.env[envVar]?.trim() || fallback}`);
|
|
76
|
+
}
|
|
77
|
+
return args;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Prefix every failure out of the ledger write path with what was being
|
|
81
|
+
* written and where (#2301 deliverable 2). `git commit-tree failed: <stderr>`
|
|
82
|
+
* on its own does not say that the thing that failed was chant recording a
|
|
83
|
+
* fact on its own branch, and the caller that swallows it is one stack frame
|
|
84
|
+
* away from printing nothing at all.
|
|
85
|
+
*/
|
|
86
|
+
function ledgerWriteError(stage, path, stderr) {
|
|
87
|
+
return new Error(`cannot write ${path} on the ${STATE_BRANCH} branch: ${stage} failed: ${(stderr ?? "").trim()}`);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Bounded retry budget for {@link writeBlobToPath}'s own internal CAS retry
|
|
91
|
+
* (#1959 finding 1). A conflict caused by some *other* env/file entry
|
|
92
|
+
* changing concurrently (the ordinary case — e.g. two operator ticks
|
|
93
|
+
* targeting different envs) is safe to resolve by simply rebuilding the tree
|
|
94
|
+
* against the new tip and retrying; this bounds how many times it does that
|
|
95
|
+
* before giving up and surfacing the conflict.
|
|
96
|
+
*/
|
|
97
|
+
const WRITE_BLOB_RETRY_ATTEMPTS = 5;
|
|
98
|
+
/**
|
|
99
|
+
* Write a blob to an arbitrary `<environment>/<filename>` path on the orphan
|
|
100
|
+
* branch, preserving every other env/file entry already on the branch.
|
|
101
|
+
*
|
|
102
|
+
* Pipeline: hash-object → mktree → commit-tree → update-ref. Factored out of
|
|
103
|
+
* `writeSnapshot` so the release ledger (#568, epic #551 "Build & deploy
|
|
104
|
+
* observability") can reuse the identical git-plumbing path for a different
|
|
105
|
+
* filename (`releases.jsonl`) under the same env directory, rather than a
|
|
106
|
+
* parallel storage mechanism.
|
|
107
|
+
*
|
|
108
|
+
* Exported (not just used internally) so ./build-ledger-store.ts (#609) can
|
|
109
|
+
* reuse this exact plumbing for a top-level directory that isn't really an
|
|
110
|
+
* "environment" (`_builds`, keyed by manifest digest rather than env name) —
|
|
111
|
+
* a build archive is promoted by digest across environments, never owned by
|
|
112
|
+
* one, so it belongs in its own digest-keyed namespace on the same orphan
|
|
113
|
+
* branch rather than duplicated per env. The parameter is still named
|
|
114
|
+
* `environment` because it is literally the first path segment / root-tree
|
|
115
|
+
* entry name this function's tree-building logic groups by; callers outside
|
|
116
|
+
* this module that pass a non-env value (like `_builds`) are relying on that
|
|
117
|
+
* generic behavior, not on any env-specific semantics.
|
|
118
|
+
*
|
|
119
|
+
* `environment` is relative to the project's ledger (#2538): a workspace
|
|
120
|
+
* member's write lands under `_members/<member>/` ({@link ledgerDir}). The
|
|
121
|
+
* write itself is {@link writeLedgerFiles} with one file.
|
|
122
|
+
*/
|
|
123
|
+
export async function writeBlobToPath(environment, filename, content, commitMessage, opts) {
|
|
124
|
+
const dir = await ledgerDir(environment, opts);
|
|
125
|
+
return writeLedgerFiles([{ path: `${dir}/${filename}`, content, ...(opts?.expectPriorPathSha !== undefined ? { expectPriorSha: opts.expectPriorPathSha } : {}) }], commitMessage, opts);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Write several files to the branch in one commit (#2538). Paths are full
|
|
129
|
+
* branch paths, so one commit can write the ledgers of several members, for
|
|
130
|
+
* example `_members/api/prod/releases.jsonl` and
|
|
131
|
+
* `_members/web/prod/releases.jsonl` together. {@link writeBlobToPath} is the
|
|
132
|
+
* one-file case with the project's own member prefix applied.
|
|
133
|
+
*
|
|
134
|
+
* Pipeline, per attempt: read the directories each path passes through at
|
|
135
|
+
* the branch tip, replace the entries, rebuild the touched trees bottom-up
|
|
136
|
+
* with `git mktree`, `commit-tree` on that tip, then CAS-update the ref.
|
|
137
|
+
* Every entry the write does not touch is carried over as it is, so the tree
|
|
138
|
+
* of an unchanged directory keeps its sha.
|
|
139
|
+
*/
|
|
140
|
+
export async function writeLedgerFiles(files, commitMessage, opts) {
|
|
141
|
+
const rt = getRuntime();
|
|
142
|
+
const cwd = opts?.cwd;
|
|
143
|
+
if (files.length === 0)
|
|
144
|
+
throw new Error("writeLedgerFiles needs at least one file");
|
|
145
|
+
// Failures name the first path; one file is the ordinary case.
|
|
146
|
+
const path = files[0].path;
|
|
147
|
+
for (const f of files) {
|
|
148
|
+
const segs = f.path.split("/");
|
|
149
|
+
if (segs.length < 2 || segs.some((seg) => seg === "")) {
|
|
150
|
+
throw new Error(`cannot write ${f.path} on the ${STATE_BRANCH} branch: a ledger path is <dir>/<file>`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// 1. Write the blobs — hash-object reads from stdin. Fed directly via
|
|
154
|
+
// spawn's stdin (no shell involved), so content is written byte-for-byte:
|
|
155
|
+
// no `sh` `echo` reinterpreting backslash-escape sequences (e.g. `\n`
|
|
156
|
+
// inside JSON, as in a serialized
|
|
157
|
+
// kubectl.kubernetes.io/last-applied-configuration annotation) and no
|
|
158
|
+
// shell-quoting dance around embedded single quotes. Content-addressed
|
|
159
|
+
// and idempotent, so this stays outside the retry loop below.
|
|
160
|
+
const blobs = [];
|
|
161
|
+
for (const f of files) {
|
|
162
|
+
const blobResult = await rt.spawn(["git", "hash-object", "-w", "--stdin"], { cwd, stdin: f.content });
|
|
163
|
+
if (blobResult.exitCode !== 0) {
|
|
164
|
+
throw ledgerWriteError("git hash-object", f.path, blobResult.stderr);
|
|
165
|
+
}
|
|
166
|
+
blobs.push({ path: f.path, sha: blobResult.stdout.trim() });
|
|
167
|
+
}
|
|
168
|
+
// Resolved once, outside the retry loop: whether the checkout can name a
|
|
169
|
+
// committer does not change between attempts (#2301).
|
|
170
|
+
const identityArgs = await ledgerCommitIdentityArgs(cwd);
|
|
171
|
+
// Creating the ledger branch is a different act from extending it, and only
|
|
172
|
+
// one of them is safe to do blind (#2309 review). With no local
|
|
173
|
+
// `chant/lifecycle`, everything below builds a tree from an empty read and
|
|
174
|
+
// commits it with no parent — a brand-new root commit that *replaces* the
|
|
175
|
+
// branch rather than appending to it, which `updateRefCAS` cannot catch
|
|
176
|
+
// because "the ref does not exist" is perfectly true locally.
|
|
177
|
+
//
|
|
178
|
+
// Until #2301 this was mostly hidden: `commit-tree` died for want of a
|
|
179
|
+
// committer before it could happen. Supplying an identity turned that loud
|
|
180
|
+
// failure into a silent branch fabrication on every writer that reaches
|
|
181
|
+
// here without a ledger — a run record, a snapshot, a release, a build
|
|
182
|
+
// manifest — and a fabricated branch is precisely the fork the guard above
|
|
183
|
+
// exists to refuse. So the guard runs here, at the one seam they all pass
|
|
184
|
+
// through, rather than at each of their call sites.
|
|
185
|
+
//
|
|
186
|
+
// Only when the branch is absent: an extend costs no fetch, and the check
|
|
187
|
+
// that matters is whether the history this is about to discard exists
|
|
188
|
+
// somewhere. `requireLifecycleLedger` allows `no-remote` and `absent`, so a
|
|
189
|
+
// genuinely first write still creates the branch.
|
|
190
|
+
if ((await getStateBranchTip(cwd)) === null) {
|
|
191
|
+
await requireLifecycleLedger({ ...(cwd ? { cwd } : {}) });
|
|
192
|
+
}
|
|
193
|
+
// 2-5. Read the trees, build the commit, and CAS-update the branch ref —
|
|
194
|
+
// retried on a conflict (#1959 finding 1). Before that retry existed a
|
|
195
|
+
// conflict from step 5's `updateRefCAS` simply threw, breaking every caller
|
|
196
|
+
// (observation-baseline.ts, snapshot.ts, build-ledger-store.ts, and — via
|
|
197
|
+
// `appendReleaseRecordLine` below — release-ledger.ts) the moment any
|
|
198
|
+
// concurrent writer touched the orphan branch, e.g. a live `chant
|
|
199
|
+
// operator` ticking a different env.
|
|
200
|
+
//
|
|
201
|
+
// Safety of a *blind* retry (same content and blob, freshly rebuilt tree)
|
|
202
|
+
// hinges on whether *our own* target paths are what actually caused the
|
|
203
|
+
// conflict:
|
|
204
|
+
// - If some OTHER path changed (the ordinary multi-env/multi-file case),
|
|
205
|
+
// our data is unaffected — rebuilding the tree from the new tip and
|
|
206
|
+
// retrying the ref update is always correct, no matter what kind of
|
|
207
|
+
// write the caller is doing (replace or append).
|
|
208
|
+
// - If one of OUR paths changed concurrently, a blind retry using
|
|
209
|
+
// content computed before that race would silently clobber the other
|
|
210
|
+
// writer's change — safe only for a caller whose content is a
|
|
211
|
+
// self-contained replacement (writeObservationBaseline, writeSnapshot,
|
|
212
|
+
// persistBuildManifest), never for a read-modify-write caller whose
|
|
213
|
+
// content already embeds a stale read (an append). Since this function
|
|
214
|
+
// can't tell those apart, it does NOT blind-retry in that case — it
|
|
215
|
+
// re-throws immediately so a read-modify-write caller's own outer retry
|
|
216
|
+
// (appendConvergeRecord, appendGateResolution, appendReleaseRecordLine
|
|
217
|
+
// below) can re-read and recompute content fresh before trying again.
|
|
218
|
+
//
|
|
219
|
+
// - A read-modify-write caller must pass `expectPriorSha`. Without it
|
|
220
|
+
// attempt 1 has no prior sha to compare, so a commit landing between the
|
|
221
|
+
// caller's baseline read and this function's first tree read reads as
|
|
222
|
+
// the ambient starting state rather than a conflict, and is overwritten
|
|
223
|
+
// from the caller's stale content.
|
|
224
|
+
const prior = new Map(files.map((f) => [f.path, f.expectPriorSha]));
|
|
225
|
+
let lastErr;
|
|
226
|
+
for (let attempt = 1; attempt <= WRITE_BLOB_RETRY_ATTEMPTS; attempt++) {
|
|
227
|
+
// 2. Read the tip once and every listing from that sha. `tip` must stay
|
|
228
|
+
// the commit parent and CAS oldValue below. Re-resolving the branch name
|
|
229
|
+
// there instead can observe a newer tip than the listings reflect, which
|
|
230
|
+
// makes the CAS succeed against a tree built from a stale read.
|
|
231
|
+
const tip = await getStateBranchTip(cwd);
|
|
232
|
+
const trees = new Map();
|
|
233
|
+
const listing = async (dir) => {
|
|
234
|
+
let entries = trees.get(dir);
|
|
235
|
+
if (!entries) {
|
|
236
|
+
entries = tip ? await listTreeAt(tip, dir, cwd) : new Map();
|
|
237
|
+
trees.set(dir, entries);
|
|
238
|
+
}
|
|
239
|
+
return entries;
|
|
240
|
+
};
|
|
241
|
+
for (const b of blobs) {
|
|
242
|
+
const { dir, name } = splitPath(b.path);
|
|
243
|
+
const current = (await listing(dir)).get(name);
|
|
244
|
+
const currentSha = current?.type === "blob" ? current.sha : null;
|
|
245
|
+
const expected = prior.get(b.path);
|
|
246
|
+
if (expected !== undefined && currentSha !== expected) {
|
|
247
|
+
// Our own path moved since the caller read it or our previous
|
|
248
|
+
// attempt started — a genuine content-level race on the exact file
|
|
249
|
+
// we're writing, not just a CAS conflict from a sibling path. Not
|
|
250
|
+
// safe to paper over here.
|
|
251
|
+
throw lastErr ?? new RefCASConflictError(`refs/heads/${STATE_BRANCH}`, expected, "target path changed concurrently");
|
|
252
|
+
}
|
|
253
|
+
prior.set(b.path, currentSha);
|
|
254
|
+
}
|
|
255
|
+
// 3. Replace the entries, listing every directory up to the root so the
|
|
256
|
+
// rebuild below has each parent in hand.
|
|
257
|
+
for (const b of blobs) {
|
|
258
|
+
const { dir, name } = splitPath(b.path);
|
|
259
|
+
(await listing(dir)).set(name, { mode: "100644", type: "blob", sha: b.sha });
|
|
260
|
+
for (let d = dir; d !== ""; d = parentDir(d))
|
|
261
|
+
await listing(parentDir(d));
|
|
262
|
+
}
|
|
263
|
+
// 4. Rebuild the touched trees, deepest first, then the root.
|
|
264
|
+
const touched = [...trees.keys()].sort((a, b) => depth(b) - depth(a) || (a < b ? -1 : 1));
|
|
265
|
+
let rootTreeSha = "";
|
|
266
|
+
for (const dir of touched) {
|
|
267
|
+
const lines = [...trees.get(dir)].map(([name, e]) => `${e.mode} ${e.type} ${e.sha}\t${name}`).join("\n");
|
|
268
|
+
const result = await rt.spawn(["git", "mktree"], { cwd, stdin: `${lines}\n` });
|
|
269
|
+
if (result.exitCode !== 0) {
|
|
270
|
+
throw ledgerWriteError(`git mktree (${dir === "" ? "root" : dir})`, path, result.stderr);
|
|
271
|
+
}
|
|
272
|
+
const sha = result.stdout.trim();
|
|
273
|
+
if (dir === "")
|
|
274
|
+
rootTreeSha = sha;
|
|
275
|
+
else
|
|
276
|
+
trees.get(parentDir(dir)).set(baseName(dir), { mode: "040000", type: "tree", sha });
|
|
277
|
+
}
|
|
278
|
+
// 5. Create the commit — parented on `tip`, the exact sha every listing
|
|
279
|
+
// was read from (see step 2), not a fresh re-resolution of the branch.
|
|
280
|
+
const parentRef = tip;
|
|
281
|
+
const parentArgs = parentRef ? ["-p", parentRef] : [];
|
|
282
|
+
const commitResult = await rt.spawn(["git", ...identityArgs, "commit-tree", ...parentArgs, "-m", commitMessage, rootTreeSha], { cwd });
|
|
283
|
+
if (commitResult.exitCode !== 0) {
|
|
284
|
+
throw ledgerWriteError("git commit-tree", path, commitResult.stderr);
|
|
285
|
+
}
|
|
286
|
+
const commitSha = commitResult.stdout.trim();
|
|
287
|
+
// 6. Update ref — CAS-guarded against `parentRef` (#1485): closes the
|
|
288
|
+
// local race two concurrent writers used to hit silently (whoever called
|
|
289
|
+
// update-ref last simply overwrote the other's tree, no error). A
|
|
290
|
+
// conflict here throws RefCASConflictError instead of clobbering.
|
|
291
|
+
try {
|
|
292
|
+
await updateRefCAS(`refs/heads/${STATE_BRANCH}`, commitSha, parentRef, { cwd });
|
|
293
|
+
return commitSha;
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
if (!(err instanceof RefCASConflictError))
|
|
297
|
+
throw err;
|
|
298
|
+
lastErr = err;
|
|
299
|
+
// Loop and rebuild against the new tip — see the safety analysis
|
|
300
|
+
// above; the top of the next iteration decides whether that's safe.
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
throw lastErr;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Read a blob from an arbitrary `<environment>/<filename>` path on the orphan
|
|
307
|
+
* branch. Returns null when the path doesn't exist (missing branch, env, or
|
|
308
|
+
* file). Sibling of `writeBlobToPath`. Exported for the same reason
|
|
309
|
+
* `writeBlobToPath` is — ./build-ledger-store.ts (#609) reads manifests back
|
|
310
|
+
* from the `_builds` namespace through this identical helper.
|
|
311
|
+
*/
|
|
312
|
+
export async function readBlobFromPath(environment, filename, opts) {
|
|
313
|
+
const rt = getRuntime();
|
|
314
|
+
const dir = await ledgerDir(environment, opts);
|
|
315
|
+
const result = await rt.spawn(["git", "show", `${STATE_BRANCH}:${dir}/${filename}`], { cwd: opts?.cwd });
|
|
316
|
+
if (result.exitCode !== 0)
|
|
317
|
+
return null;
|
|
318
|
+
return result.stdout;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Read the blob SHA stored at `<environment>/<filename>` on the orphan branch,
|
|
322
|
+
* or `null` if absent. Sibling of `readBlobFromPath` returning the
|
|
323
|
+
* content-address rather than the content. A read-modify-write ledger append
|
|
324
|
+
* pairs this with {@link readBlobBySha} to pin its baseline read to an exact
|
|
325
|
+
* sha, then passes that sha as `writeBlobToPath`'s `expectPriorPathSha`.
|
|
326
|
+
*/
|
|
327
|
+
export async function readPathSha(environment, filename, opts) {
|
|
328
|
+
const rt = getRuntime();
|
|
329
|
+
const dir = await ledgerDir(environment, opts);
|
|
330
|
+
const result = await rt.spawn(["git", "rev-parse", "--verify", `${STATE_BRANCH}:${dir}/${filename}`], { cwd: opts?.cwd });
|
|
331
|
+
if (result.exitCode !== 0)
|
|
332
|
+
return null;
|
|
333
|
+
return result.stdout.trim() || null;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Storage key for a snapshot on the orphan branch. Single-stack projects key by
|
|
337
|
+
* lexicon (`<env>/<lexicon>.json`, unchanged). A multi-stack project (see
|
|
338
|
+
* `stacks` in ChantConfig, #932) folds the stack in as `<stack>__<lexicon>`
|
|
339
|
+
* (`<env>/<stack>__<lexicon>.json`) so sibling stacks that deploy the same
|
|
340
|
+
* lexicon don't overwrite each other's snapshots. The `__` separator can't
|
|
341
|
+
* collide with a lexicon name (lexicons are single tokens) and round-trips
|
|
342
|
+
* through `readSnapshot`/`readEnvironmentSnapshots` unchanged.
|
|
343
|
+
*/
|
|
344
|
+
export function snapshotStorageKey(lexicon, stack) {
|
|
345
|
+
return stack ? `${stack}__${lexicon}` : lexicon;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Write a state snapshot JSON to the orphan branch.
|
|
349
|
+
*
|
|
350
|
+
* Pipeline: hash-object → mktree → commit-tree → update-ref
|
|
351
|
+
*/
|
|
352
|
+
export async function writeSnapshot(environment, lexicon, json, opts) {
|
|
353
|
+
return writeBlobToPath(environment, `${lexicon}.json`, json, "State snapshot", opts);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Read a snapshot from the orphan branch.
|
|
357
|
+
*/
|
|
358
|
+
export async function readSnapshot(environment, lexicon, opts) {
|
|
359
|
+
return readBlobFromPath(environment, `${lexicon}.json`, opts);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Read a snapshot at a specific orphan-branch commit (#822). `readSnapshot` reads
|
|
363
|
+
* the branch tip; this reads `<ref>:<env>/<lexicon>.json` for any commit `ref`
|
|
364
|
+
* (as listed by {@link listSnapshots}), so two historical snapshots can be diffed.
|
|
365
|
+
* Returns null if that env/lexicon wasn't captured at `ref`.
|
|
366
|
+
*/
|
|
367
|
+
export async function readSnapshotAt(environment, lexicon, ref, opts) {
|
|
368
|
+
const rt = getRuntime();
|
|
369
|
+
const dir = await ledgerDir(environment, opts);
|
|
370
|
+
const result = await rt.spawn(["git", "show", `${ref}:${dir}/${lexicon}.json`], { cwd: opts?.cwd });
|
|
371
|
+
if (result.exitCode !== 0)
|
|
372
|
+
return null;
|
|
373
|
+
return result.stdout;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Append one immutable release record line to `<environment>/releases.jsonl`
|
|
377
|
+
* on the orphan branch (#568, epic #551 "Build & deploy observability"). Same
|
|
378
|
+
* git-plumbing path `writeSnapshot` uses, just a different filename under the
|
|
379
|
+
* env directory and append-only (JSON Lines) instead of replace-whole-file:
|
|
380
|
+
* each deploy adds one line, never rewrites a previous one, so the ledger
|
|
381
|
+
* stays a durable, append-only history rather than a point-in-time snapshot.
|
|
382
|
+
*
|
|
383
|
+
* Low-level plumbing over an already-serialized line; ../release-ledger.ts's
|
|
384
|
+
* `appendReleaseRecord` is the typed, validated public API most callers want
|
|
385
|
+
* — named distinctly here (`ReleaseRecordLine`) to avoid re-export ambiguity
|
|
386
|
+
* between the two modules.
|
|
387
|
+
*
|
|
388
|
+
* Returns the new orphan-branch commit SHA — the caller still owns pushing
|
|
389
|
+
* via `pushLifecycle` under the same concurrent-write lease `writeSnapshot`
|
|
390
|
+
* uses.
|
|
391
|
+
*
|
|
392
|
+
* Retries the whole read-modify-append cycle on `RefCASConflictError`
|
|
393
|
+
* (#1959 finding 1), the same shape `appendConvergeRecord`
|
|
394
|
+
* (./converge-ledger.ts) and `appendGateResolution` (./gate-ledger.ts) use
|
|
395
|
+
* for their own append-only ledgers: `writeBlobToPath`'s own retry only
|
|
396
|
+
* absorbs a conflict caused by some *other* env/file changing — a conflict
|
|
397
|
+
* on this exact `releases.jsonl` (e.g. two deploys to the same env racing)
|
|
398
|
+
* needs `existing` re-read fresh so the appended line list is rebuilt onto
|
|
399
|
+
* whatever the other writer just committed, not silently dropped by
|
|
400
|
+
* retrying with a blob computed from a stale read.
|
|
401
|
+
*
|
|
402
|
+
* The baseline read must be `readPathSha` + `readBlobBySha` rather than
|
|
403
|
+
* `readBlobFromPath`, so the exact sha `existing` came from can be passed as
|
|
404
|
+
* `expectPriorPathSha`. See `writeBlobToPath` for the race that closes.
|
|
405
|
+
*/
|
|
406
|
+
export async function appendReleaseRecordLine(environment, recordJson, opts) {
|
|
407
|
+
const filename = "releases.jsonl";
|
|
408
|
+
let lastErr;
|
|
409
|
+
for (let attempt = 1; attempt <= WRITE_BLOB_RETRY_ATTEMPTS; attempt++) {
|
|
410
|
+
try {
|
|
411
|
+
const priorSha = await readPathSha(environment, filename, opts);
|
|
412
|
+
const existing = priorSha ? await readBlobBySha(priorSha, opts) : null;
|
|
413
|
+
const content = `${existing ? `${existing.replace(/\n$/, "")}\n` : ""}${recordJson}\n`;
|
|
414
|
+
return await writeBlobToPath(environment, filename, content, "Release record", {
|
|
415
|
+
...opts,
|
|
416
|
+
expectPriorPathSha: priorSha,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
if (!(err instanceof RefCASConflictError))
|
|
421
|
+
throw err;
|
|
422
|
+
lastErr = err;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
throw lastErr;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Read every release record line for `environment` from the orphan branch,
|
|
429
|
+
* oldest first, as raw JSON strings. Returns `[]` when no ledger exists yet
|
|
430
|
+
* for that env (never throws — a component that has never recorded a deploy
|
|
431
|
+
* is a normal, expected state). ../release-ledger.ts's `readReleaseLedger`
|
|
432
|
+
* parses/validates these lines into typed `ReleaseRecord`s.
|
|
433
|
+
*/
|
|
434
|
+
export async function readReleaseLedgerLines(environment, opts) {
|
|
435
|
+
const content = await readBlobFromPath(environment, "releases.jsonl", opts);
|
|
436
|
+
if (!content)
|
|
437
|
+
return [];
|
|
438
|
+
return content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* List every environment that has a release ledger on the orphan branch —
|
|
442
|
+
* the root-level directories under `chant/lifecycle` that carry a
|
|
443
|
+
* `releases.jsonl` entry. Used by `chant components status` (no env arg) to
|
|
444
|
+
* discover which environments to report on.
|
|
445
|
+
*/
|
|
446
|
+
export async function listLedgerEnvironments(opts) {
|
|
447
|
+
const envs = [];
|
|
448
|
+
for (const name of await listLedgerTrees(opts)) {
|
|
449
|
+
const hasLedger = await readBlobFromPath(name, "releases.jsonl", opts);
|
|
450
|
+
if (hasLedger)
|
|
451
|
+
envs.push(name);
|
|
452
|
+
}
|
|
453
|
+
return envs.sort();
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* The directories directly under the ledger's base: the root for the flat
|
|
457
|
+
* layout, `_members/<member>/` for a member. `[]` when the branch, or the
|
|
458
|
+
* base, is absent.
|
|
459
|
+
*/
|
|
460
|
+
export async function listLedgerTrees(opts) {
|
|
461
|
+
const tip = await getStateBranchTip(opts?.cwd);
|
|
462
|
+
if (!tip)
|
|
463
|
+
return [];
|
|
464
|
+
const rt = getRuntime();
|
|
465
|
+
// A member lists the directories under its own `_members/<member>/`; the
|
|
466
|
+
// flat layout lists the root, where `_members` holds no releases.jsonl and
|
|
467
|
+
// so never reads as an environment (#2538).
|
|
468
|
+
const base = await ledgerDir("", opts);
|
|
469
|
+
// `--full-tree`: from a subdirectory, a bare `ls-tree` lists only that
|
|
470
|
+
// subdirectory's part of the tree (#2550).
|
|
471
|
+
const rootResult = await rt.spawn(["git", "ls-tree", "--full-tree", base === "" ? STATE_BRANCH : `${STATE_BRANCH}:${base}`], { cwd: opts?.cwd });
|
|
472
|
+
if (rootResult.exitCode !== 0)
|
|
473
|
+
return [];
|
|
474
|
+
const trees = [];
|
|
475
|
+
for (const line of rootResult.stdout.trim().split("\n").filter(Boolean)) {
|
|
476
|
+
const match = line.match(/^(\d+)\s+(\w+)\s+([0-9a-f]+)\t(.+)$/);
|
|
477
|
+
if (match && match[2] === "tree")
|
|
478
|
+
trees.push(match[4]);
|
|
479
|
+
}
|
|
480
|
+
return trees;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* List every filename directly under `<dir>/` on the orphan branch (no
|
|
484
|
+
* recursion), or `[]` when the branch/directory doesn't exist yet. Generic
|
|
485
|
+
* sibling of `readEnvironmentSnapshots`'s inline file listing, factored out
|
|
486
|
+
* so ./build-ledger-store.ts (#609) can enumerate every persisted build
|
|
487
|
+
* manifest (`_builds/<digest>.json`) without duplicating this `git ls-tree
|
|
488
|
+
* --name-only` call.
|
|
489
|
+
*/
|
|
490
|
+
export async function listFilesInDir(dir, opts) {
|
|
491
|
+
const tip = await getStateBranchTip(opts?.cwd);
|
|
492
|
+
if (!tip)
|
|
493
|
+
return [];
|
|
494
|
+
const rt = getRuntime();
|
|
495
|
+
const path = await ledgerDir(dir, opts);
|
|
496
|
+
const lsResult = await rt.spawn(["git", "ls-tree", "--full-tree", "--name-only", `${STATE_BRANCH}:${path}/`], { cwd: opts?.cwd });
|
|
497
|
+
if (lsResult.exitCode !== 0)
|
|
498
|
+
return [];
|
|
499
|
+
return lsResult.stdout.trim().split("\n").filter(Boolean);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Read all snapshots for an environment (all lexicons).
|
|
503
|
+
*/
|
|
504
|
+
export async function readEnvironmentSnapshots(environment, opts) {
|
|
505
|
+
const rt = getRuntime();
|
|
506
|
+
const snapshots = new Map();
|
|
507
|
+
// List files in the environment directory
|
|
508
|
+
const dir = await ledgerDir(environment, opts);
|
|
509
|
+
const lsResult = await rt.spawn(["git", "ls-tree", "--full-tree", "--name-only", `${STATE_BRANCH}:${dir}/`], { cwd: opts?.cwd });
|
|
510
|
+
if (lsResult.exitCode !== 0)
|
|
511
|
+
return snapshots;
|
|
512
|
+
const files = lsResult.stdout.trim().split("\n").filter(Boolean);
|
|
513
|
+
for (const file of files) {
|
|
514
|
+
if (file.endsWith(".json")) {
|
|
515
|
+
const lexicon = file.replace(/\.json$/, "");
|
|
516
|
+
const content = await readSnapshot(environment, lexicon, opts);
|
|
517
|
+
if (content)
|
|
518
|
+
snapshots.set(lexicon, content);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return snapshots;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* List snapshot history from the orphan branch.
|
|
525
|
+
*/
|
|
526
|
+
export async function listSnapshots(opts) {
|
|
527
|
+
const rt = getRuntime();
|
|
528
|
+
// A member sees the commits that touched its own ledger (#2538); the flat
|
|
529
|
+
// layout keeps today's whole-branch history.
|
|
530
|
+
const base = await ledgerDir("", opts);
|
|
531
|
+
const result = await rt.spawn(
|
|
532
|
+
// `:(top)`: a pathspec is otherwise relative to the directory git runs in.
|
|
533
|
+
["git", "log", "--format=%H %aI %s", STATE_BRANCH, ...(base === "" ? [] : ["--", `:(top)${base}`])], { cwd: opts?.cwd });
|
|
534
|
+
if (result.exitCode !== 0)
|
|
535
|
+
return [];
|
|
536
|
+
return result.stdout
|
|
537
|
+
.trim()
|
|
538
|
+
.split("\n")
|
|
539
|
+
.filter(Boolean)
|
|
540
|
+
.map((line) => {
|
|
541
|
+
const [commit, date, ...rest] = line.split(" ");
|
|
542
|
+
return { commit, date, message: rest.join(" ") };
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Thrown by pushLifecycle when the remote chant/lifecycle branch has moved since
|
|
547
|
+
* the local snapshot was prepared — i.e. another snapshot for this or a
|
|
548
|
+
* different env was pushed concurrently. The caller should fetch and retry.
|
|
549
|
+
*/
|
|
550
|
+
export class StaleLifecycleBranchError extends Error {
|
|
551
|
+
constructor(expected, stderr) {
|
|
552
|
+
super("chant/lifecycle remote branch has moved since this run started — " +
|
|
553
|
+
"another snapshot was pushed concurrently. " +
|
|
554
|
+
`git stderr: ${stderr.trim()}`);
|
|
555
|
+
this.name = "StaleLifecycleBranchError";
|
|
556
|
+
this.expected = expected;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Look up the remote-tracking SHA for chant/lifecycle, if any. Returns null when
|
|
561
|
+
* the remote ref doesn't exist locally yet (e.g. first-ever snapshot).
|
|
562
|
+
*/
|
|
563
|
+
export async function getRemoteLifecycleBranchSha(remote, opts) {
|
|
564
|
+
const rt = getRuntime();
|
|
565
|
+
const ref = `refs/remotes/${remote}/${STATE_BRANCH}`;
|
|
566
|
+
const result = await rt.spawn(["git", "rev-parse", "--verify", ref], { cwd: opts?.cwd });
|
|
567
|
+
if (result.exitCode !== 0)
|
|
568
|
+
return null;
|
|
569
|
+
return result.stdout.trim() || null;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Point `refs/remotes/<remote>/chant/lifecycle` at `sha`, the remote tip this
|
|
573
|
+
* checkout has just seen (#3391).
|
|
574
|
+
*
|
|
575
|
+
* The push's lease is that ref, and git only keeps it up to date when the
|
|
576
|
+
* remote's configured fetch refspec covers `chant/lifecycle`. A single-branch
|
|
577
|
+
* clone (`git clone --single-branch`, a CI checkout of one ref, a shallow
|
|
578
|
+
* clone) has a refspec for its one branch only, so neither the explicit
|
|
579
|
+
* `chant/lifecycle:chant/lifecycle` fetch nor a successful push touches the
|
|
580
|
+
* tracking ref. The first push of a new branch then succeeds against an empty
|
|
581
|
+
* lease, and every later one is refused as stale against a remote that holds
|
|
582
|
+
* exactly what this checkout last pushed. Recording the tip ourselves, only at
|
|
583
|
+
* the two moments it is known (a fetch that fast-forwarded or found the local
|
|
584
|
+
* branch ahead, and a push that landed), keeps the lease honest without
|
|
585
|
+
* widening the clone's refspec. It is never set from a diverged fetch: that is
|
|
586
|
+
* the case the lease exists to refuse.
|
|
587
|
+
*/
|
|
588
|
+
async function trackRemoteLifecycle(remote, sha, cwd) {
|
|
589
|
+
const rt = getRuntime();
|
|
590
|
+
await rt.spawn(["git", "update-ref", `refs/remotes/${remote}/${STATE_BRANCH}`, sha], { cwd, env: C_LOCALE_ENV });
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Push the state branch to the ledger remote with --force-with-lease and say
|
|
594
|
+
* what happened (#3391). Never throws for a git refusal; {@link pushLifecycle}
|
|
595
|
+
* is the throwing form most callers use.
|
|
596
|
+
*/
|
|
597
|
+
export async function pushLifecycleStatus(opts) {
|
|
598
|
+
const rt = getRuntime();
|
|
599
|
+
const cwd = opts?.cwd;
|
|
600
|
+
const found = await ledgerRemote(cwd);
|
|
601
|
+
if (found === null)
|
|
602
|
+
return { status: "no-remote" };
|
|
603
|
+
if ("failed" in found)
|
|
604
|
+
return { status: "failed", remote: "(unknown)", stderr: found.stderr };
|
|
605
|
+
const { remote } = found;
|
|
606
|
+
// Capture the lease SHA — if null, the remote ref doesn't exist yet
|
|
607
|
+
// (first-time push) and we send `--force-with-lease=ref:` (empty SHA),
|
|
608
|
+
// which git interprets as "ref does not exist on remote".
|
|
609
|
+
const expected = await getRemoteLifecycleBranchSha(remote, opts);
|
|
610
|
+
const lease = `refs/heads/${STATE_BRANCH}:${expected ?? ""}`;
|
|
611
|
+
const tip = await getStateBranchTip(cwd);
|
|
612
|
+
const pushResult = await rt.spawn(["git", "push", `--force-with-lease=${lease}`, remote, `${STATE_BRANCH}:${STATE_BRANCH}`], { cwd, env: C_LOCALE_ENV });
|
|
613
|
+
if (pushResult.exitCode !== 0) {
|
|
614
|
+
const stderr = (pushResult.stderr ?? "").trim();
|
|
615
|
+
if (stderr.includes("stale info") ||
|
|
616
|
+
stderr.includes("rejected") ||
|
|
617
|
+
stderr.includes("non-fast-forward")) {
|
|
618
|
+
return { status: "stale", remote, expected, stderr };
|
|
619
|
+
}
|
|
620
|
+
return { status: "failed", remote, stderr };
|
|
621
|
+
}
|
|
622
|
+
if (tip)
|
|
623
|
+
await trackRemoteLifecycle(remote, tip, cwd);
|
|
624
|
+
return { status: "pushed", remote, commit: tip ?? "" };
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Push the state branch to remote with --force-with-lease.
|
|
628
|
+
*
|
|
629
|
+
* If the remote chant/lifecycle ref has advanced past the local remote-tracking
|
|
630
|
+
* SHA captured at the start of this push, the push is rejected and we throw
|
|
631
|
+
* StaleLifecycleBranchError so the caller can surface a recovery hint.
|
|
632
|
+
*
|
|
633
|
+
* Returns false (without throwing) when no remote is configured, or when git
|
|
634
|
+
* refused the push for a reason other than a stale lease.
|
|
635
|
+
*
|
|
636
|
+
* The remote is the ledger's ({@link ledgerRemote}: `origin` when there is one),
|
|
637
|
+
* the same one {@link fetchLifecycleStatus} reads, and a push that lands
|
|
638
|
+
* records the tip it pushed as the remote-tracking ref, so the next push's
|
|
639
|
+
* lease is right in a single-branch clone too (#3391).
|
|
640
|
+
*/
|
|
641
|
+
export async function pushLifecycle(opts) {
|
|
642
|
+
const result = await pushLifecycleStatus(opts);
|
|
643
|
+
if (result.status === "stale")
|
|
644
|
+
throw new StaleLifecycleBranchError(result.expected, result.stderr);
|
|
645
|
+
return result.status === "pushed";
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* One line on why a push did not land, for a write document's `notPushed`
|
|
649
|
+
* (#3391), or null when it did.
|
|
650
|
+
*/
|
|
651
|
+
export function describeLifecyclePush(result) {
|
|
652
|
+
const said = (stderr) => (stderr ? `; git said: ${stderr.split("\n").find((l) => l.trim()) ?? stderr}` : "");
|
|
653
|
+
switch (result.status) {
|
|
654
|
+
case "pushed":
|
|
655
|
+
return null;
|
|
656
|
+
case "no-remote":
|
|
657
|
+
return `this repository has no remote, so ${STATE_BRANCH} stays local`;
|
|
658
|
+
case "stale":
|
|
659
|
+
return `"${result.remote}" holds ${STATE_BRANCH} commits this checkout has not fetched, so the push was refused; fetch ${STATE_BRANCH} and write again${said(result.stderr)}`;
|
|
660
|
+
case "failed":
|
|
661
|
+
return `the push of ${STATE_BRANCH} to "${result.remote}" failed${said(result.stderr)}`;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Push the ledger branch and say, in one line, why it did not land (#2310,
|
|
666
|
+
* #3677). No remote reads as {@link LIFECYCLE_LOCAL_NOTE}; a push that git
|
|
667
|
+
* refused or that failed keeps its own reason, so a renderer can warn about
|
|
668
|
+
* that case alone. Never throws.
|
|
669
|
+
*/
|
|
670
|
+
export async function pushLifecycleReport(opts) {
|
|
671
|
+
try {
|
|
672
|
+
const result = await pushLifecycleStatus(opts);
|
|
673
|
+
switch (result.status) {
|
|
674
|
+
case "pushed":
|
|
675
|
+
return { pushed: true };
|
|
676
|
+
case "no-remote":
|
|
677
|
+
return { pushed: false, pushWarning: LIFECYCLE_LOCAL_NOTE };
|
|
678
|
+
case "stale":
|
|
679
|
+
return { pushed: false, pushWarning: new StaleLifecycleBranchError(result.expected, result.stderr).message };
|
|
680
|
+
case "failed":
|
|
681
|
+
return { pushed: false, pushWarning: describeLifecyclePush(result) ?? "the push did not land" };
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
catch (err) {
|
|
685
|
+
return { pushed: false, pushWarning: err instanceof Error ? err.message : String(err) };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Fetch the state branch from remote.
|
|
690
|
+
*/
|
|
691
|
+
export async function fetchLifecycle(opts) {
|
|
692
|
+
return (await fetchLifecycleStatus(opts)).status === "fetched";
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* git's wording when the remote answered and simply does not carry the ref.
|
|
696
|
+
*
|
|
697
|
+
* Matched against output forced to `LC_ALL=C` ({@link C_LOCALE_ENV}) — both of
|
|
698
|
+
* these strings are in git's translation catalogs, so on a distro git built
|
|
699
|
+
* with NLS and a non-C `LANG` an unpinned locale would fail to match and send
|
|
700
|
+
* a project's genuine first gate down the `unreachable` path, refusing it.
|
|
701
|
+
*/
|
|
702
|
+
const NO_SUCH_REMOTE_REF_RE = /couldn't find remote ref|no such ref was fetched/i;
|
|
703
|
+
/** git's wording when the refspec would not fast-forward the local branch. Same locale caveat. */
|
|
704
|
+
const NON_FAST_FORWARD_RE = /non-fast-forward|rejected/i;
|
|
705
|
+
/**
|
|
706
|
+
* `git` invocations here are parsed by their output, so they are pinned to the
|
|
707
|
+
* C locale. `LC_ALL` alone is enough — it outranks `LC_MESSAGES` and `LANG` —
|
|
708
|
+
* but `LANGUAGE` is GNU gettext's own override and outranks all of them, so it
|
|
709
|
+
* is cleared too.
|
|
710
|
+
*/
|
|
711
|
+
const C_LOCALE_ENV = { LC_ALL: "C", LANGUAGE: "" };
|
|
712
|
+
/**
|
|
713
|
+
* The remote the ledger lives on.
|
|
714
|
+
*
|
|
715
|
+
* `origin` when it exists, rather than whichever name sorts first — a repo
|
|
716
|
+
* with a `backup` remote alongside `origin` would otherwise fetch the ledger
|
|
717
|
+
* from `backup`. A non-zero exit is *not* read as "no remote": a git that
|
|
718
|
+
* failed to answer is not evidence that a project is single-machine, and
|
|
719
|
+
* treating it as one is what lets an unreadable ledger look empty.
|
|
720
|
+
*/
|
|
721
|
+
async function ledgerRemote(cwd) {
|
|
722
|
+
const rt = getRuntime();
|
|
723
|
+
const result = await rt.spawn(["git", "remote"], { cwd, env: C_LOCALE_ENV });
|
|
724
|
+
if (result.exitCode !== 0)
|
|
725
|
+
return { failed: true, stderr: (result.stderr ?? "").trim() };
|
|
726
|
+
const names = result.stdout.trim().split("\n").map((n) => n.trim()).filter(Boolean);
|
|
727
|
+
if (names.length === 0)
|
|
728
|
+
return null;
|
|
729
|
+
return { remote: names.includes("origin") ? "origin" : names[0] };
|
|
730
|
+
}
|
|
731
|
+
/** A scratch ref this module owns, used to resolve the remote tip without touching the ledger branch. */
|
|
732
|
+
const PROBE_REF = "refs/chant/lifecycle-probe";
|
|
733
|
+
/**
|
|
734
|
+
* Fetch `chant/lifecycle` and say what happened, in the terms
|
|
735
|
+
* {@link LifecycleFetchStatus} defines.
|
|
736
|
+
*
|
|
737
|
+
* The refspec is deliberately un-forced (`chant/lifecycle:chant/lifecycle`,
|
|
738
|
+
* no leading `+`), so a local branch holding facts the remote does not have is
|
|
739
|
+
* never rewound by a read.
|
|
740
|
+
*
|
|
741
|
+
* git reports "non-fast-forward" for two states that could not be more
|
|
742
|
+
* different, which is why the rejection is not taken at face value (#2309
|
|
743
|
+
* review): a local branch *ahead* of the remote — the ordinary state right
|
|
744
|
+
* after an append whose push has not landed yet — and a local branch that has
|
|
745
|
+
* genuinely *forked*. Resolving the remote tip into a scratch ref and asking
|
|
746
|
+
* whether it is an ancestor of the local tip separates them. Reading a fork as
|
|
747
|
+
* the ledger is what destroys approvals; refusing an append that is merely
|
|
748
|
+
* ahead would break the retry that is supposed to repair a failed push.
|
|
749
|
+
*/
|
|
750
|
+
export async function fetchLifecycleStatus(opts) {
|
|
751
|
+
const rt = getRuntime();
|
|
752
|
+
const cwd = opts?.cwd;
|
|
753
|
+
const found = await ledgerRemote(cwd);
|
|
754
|
+
if (found === null)
|
|
755
|
+
return { status: "no-remote" };
|
|
756
|
+
if ("failed" in found) {
|
|
757
|
+
return { status: "unreachable", remote: "(unknown)", stderr: found.stderr };
|
|
758
|
+
}
|
|
759
|
+
const { remote } = found;
|
|
760
|
+
const fetchResult = await rt.spawn(["git", "fetch", remote, `${STATE_BRANCH}:${STATE_BRANCH}`], { cwd, env: C_LOCALE_ENV });
|
|
761
|
+
if (fetchResult.exitCode === 0) {
|
|
762
|
+
// The local branch is now the remote's tip; record it for the push's lease (#3391).
|
|
763
|
+
const tip = await getStateBranchTip(cwd);
|
|
764
|
+
if (tip)
|
|
765
|
+
await trackRemoteLifecycle(remote, tip, cwd);
|
|
766
|
+
return { status: "fetched" };
|
|
767
|
+
}
|
|
768
|
+
const stderr = (fetchResult.stderr ?? "").trim();
|
|
769
|
+
if (NO_SUCH_REMOTE_REF_RE.test(stderr))
|
|
770
|
+
return { status: "absent", remote };
|
|
771
|
+
if (!NON_FAST_FORWARD_RE.test(stderr))
|
|
772
|
+
return { status: "unreachable", remote, stderr };
|
|
773
|
+
// Rejected. Resolve the remote tip into our own scratch ref (forced: the ref
|
|
774
|
+
// is this module's, and nothing reads it but the next two lines) and ask
|
|
775
|
+
// whether the local branch already contains it.
|
|
776
|
+
const probe = await rt.spawn(["git", "fetch", "--force", remote, `${STATE_BRANCH}:${PROBE_REF}`], { cwd, env: C_LOCALE_ENV });
|
|
777
|
+
if (probe.exitCode !== 0) {
|
|
778
|
+
return { status: "unreachable", remote, stderr: (probe.stderr ?? "").trim() || stderr };
|
|
779
|
+
}
|
|
780
|
+
const contains = await rt.spawn(["git", "merge-base", "--is-ancestor", PROBE_REF, `refs/heads/${STATE_BRANCH}`], { cwd, env: C_LOCALE_ENV });
|
|
781
|
+
const remoteTip = contains.exitCode === 0 ? (await rt.spawn(["git", "rev-parse", "--verify", PROBE_REF], { cwd, env: C_LOCALE_ENV })).stdout.trim() : "";
|
|
782
|
+
await rt.spawn(["git", "update-ref", "-d", PROBE_REF], { cwd, env: C_LOCALE_ENV });
|
|
783
|
+
if (contains.exitCode === 0) {
|
|
784
|
+
// Ahead of the remote: its tip is in our history, so it is the right lease (#3391).
|
|
785
|
+
if (remoteTip)
|
|
786
|
+
await trackRemoteLifecycle(remote, remoteTip, cwd);
|
|
787
|
+
return { status: "ahead", remote };
|
|
788
|
+
}
|
|
789
|
+
return { status: "diverged", remote, stderr };
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Thrown when the ledger branch cannot be read, rather than being reported as
|
|
793
|
+
* empty (#2303).
|
|
794
|
+
*/
|
|
795
|
+
export class LifecycleLedgerUnreadableError extends Error {
|
|
796
|
+
constructor(reason, remote, stderr, detail) {
|
|
797
|
+
super(`the ${STATE_BRANCH} ledger branch ${detail}, so what is on it cannot be read. ` +
|
|
798
|
+
`Refusing rather than treating an unreadable ledger as an empty one.` +
|
|
799
|
+
(reason === "diverged"
|
|
800
|
+
? `\n Two histories claim to be the ledger; appending to either one drops the other's facts. ` +
|
|
801
|
+
`Inspect both before doing anything else:\n` +
|
|
802
|
+
` git fetch ${remote} ${STATE_BRANCH}:refs/chant/remote-ledger\n` +
|
|
803
|
+
` git log --oneline ${STATE_BRANCH} refs/chant/remote-ledger`
|
|
804
|
+
: `\n Fetch it and retry: git fetch ${remote} ${STATE_BRANCH}:${STATE_BRANCH}`) +
|
|
805
|
+
(stderr ? `\n git said: ${stderr.split("\n")[0]}` : ""));
|
|
806
|
+
this.reason = reason;
|
|
807
|
+
this.remote = remote;
|
|
808
|
+
this.stderr = stderr;
|
|
809
|
+
this.name = "LifecycleLedgerUnreadableError";
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Bring the ledger branch into this checkout before reading or appending to
|
|
814
|
+
* it, and refuse when it cannot be (#2303).
|
|
815
|
+
*
|
|
816
|
+
* The gate's read is the one that has to be right: `evaluateGate` decides
|
|
817
|
+
* whether to walk through or to record a *new* pending fact purely from what
|
|
818
|
+
* the ledger says, so a read that reports history it cannot see as an empty
|
|
819
|
+
* ledger makes the second run of an already-approved commit record a second
|
|
820
|
+
* pending fact and gate again. That is what a GitLab CI retry did on
|
|
821
|
+
* INTENTIUS/choudoufu#1026 — two pending records for one commit, with
|
|
822
|
+
* different expiries — because a GitLab checkout fetches the pipeline's own
|
|
823
|
+
* ref at depth 20 and nothing else. `GIT_DEPTH: 0` does not help: GitLab
|
|
824
|
+
* fetches refspecs, not all branches.
|
|
825
|
+
*
|
|
826
|
+
* There was a separate, laxer guard for the read until #2309's review, on the
|
|
827
|
+
* reasoning that a checkout which already has the branch "has real history to
|
|
828
|
+
* read". That reasoning is only sound when the local branch *is* the remote's
|
|
829
|
+
* history, and it let the worst case through: on a fork, the gate read a
|
|
830
|
+
* ledger missing every remote approval, recorded a fresh pending fact, and
|
|
831
|
+
* pushed. The push was not protected either — a rejected non-fast-forward
|
|
832
|
+
* fetch still updates `refs/remotes/<remote>/chant/lifecycle` opportunistically
|
|
833
|
+
* in a full clone, so `pushLifecycle`'s `--force-with-lease` matched the tip
|
|
834
|
+
* it had just learned and forced. Verified on git 2.50.1: a remote holding a
|
|
835
|
+
* pending fact and an approval ended up holding neither.
|
|
836
|
+
*
|
|
837
|
+
* So there is one rule now, for readers and writers alike:
|
|
838
|
+
*
|
|
839
|
+
* - `diverged` always refuses. A fork is not the ledger, and nothing good
|
|
840
|
+
* comes of reading one as if it were.
|
|
841
|
+
* - `unreachable` refuses only when the checkout has no local history at all,
|
|
842
|
+
* which is the case that would otherwise read as an empty ledger. With
|
|
843
|
+
* local history and no answer from the remote, the read proceeds and the
|
|
844
|
+
* push's lease fails safely on its own — refusing there would strand every
|
|
845
|
+
* offline run.
|
|
846
|
+
* - `fetched`, `no-remote`, `absent` and `ahead` all proceed. `ahead` is the
|
|
847
|
+
* ordinary state right after an append whose push has not landed, and is
|
|
848
|
+
* exactly what a retry needs to be able to repair.
|
|
849
|
+
*/
|
|
850
|
+
export async function requireLifecycleLedger(opts) {
|
|
851
|
+
const hadLocal = (await getStateBranchTip(opts?.cwd)) !== null;
|
|
852
|
+
const result = await fetchLifecycleStatus(opts);
|
|
853
|
+
if (result.status === "diverged") {
|
|
854
|
+
throw new LifecycleLedgerUnreadableError("diverged", result.remote, result.stderr, `has diverged from the copy on "${result.remote}" — neither contains the other`);
|
|
855
|
+
}
|
|
856
|
+
if (result.status === "unreachable" && !hadLocal) {
|
|
857
|
+
throw new LifecycleLedgerUnreadableError("unreachable", result.remote, result.stderr, `is not in this checkout and could not be fetched from "${result.remote}"`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
// ── Generic ref CAS (#1485) ──────────────────────────────────────────────────
|
|
861
|
+
//
|
|
862
|
+
// `writeBlobToPath`'s own `update-ref` call (above) had no compare-and-swap
|
|
863
|
+
// until this issue: two concurrent local writers could each build a tree from
|
|
864
|
+
// what they read as "current", and whichever called `update-ref` last simply
|
|
865
|
+
// overwrote the ref with no error — the other writer's change vanished
|
|
866
|
+
// silently. The primitives below give any caller (this module's own
|
|
867
|
+
// `writeBlobToPath`, and `./lease.ts`'s lease ref) a compare-and-swap guard
|
|
868
|
+
// building on what `git update-ref` already supports natively: pass the
|
|
869
|
+
// value you last observed as `<oldvalue>`, and the update only lands if the
|
|
870
|
+
// ref still points there. No lock file — `update-ref` itself is already an
|
|
871
|
+
// atomic local mutex (lockfile-then-rename under the hood), so this is
|
|
872
|
+
// exactly the "extend the ref-write helper" shape, not a second mechanism.
|
|
873
|
+
/**
|
|
874
|
+
* Thrown by {@link updateRefCAS}/{@link deleteRefCAS} when `ref` no longer
|
|
875
|
+
* points at the `oldValue` the caller last observed — another writer moved
|
|
876
|
+
* it concurrently. Deliberately a distinct type from a generic git failure so
|
|
877
|
+
* callers (a lease acquire, a retried ledger append) can tell "I lost a race"
|
|
878
|
+
* from "git itself failed" and react differently to each.
|
|
879
|
+
*/
|
|
880
|
+
export class RefCASConflictError extends Error {
|
|
881
|
+
constructor(ref, expected, stderr) {
|
|
882
|
+
super(`ref "${ref}" moved concurrently — expected ${expected ?? "(ref must not exist)"}, ` +
|
|
883
|
+
`but another writer updated it first. git stderr: ${stderr.trim()}`);
|
|
884
|
+
this.ref = ref;
|
|
885
|
+
this.expected = expected;
|
|
886
|
+
this.name = "RefCASConflictError";
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Thrown by {@link updateRefCAS}/{@link deleteRefCAS} when the ref update
|
|
891
|
+
* failed because git found a stale `.lock` file already sitting next to the
|
|
892
|
+
* ref (#1959 finding 2) — what a `chant operator`/`chant approve`/etc.
|
|
893
|
+
* process leaves behind when it is killed (SIGKILL, OOM, `kill -9`) mid-write,
|
|
894
|
+
* *before* `git update-ref`'s own lockfile-then-rename completes. This is
|
|
895
|
+
* exactly the crash this feature must recover from, and it is NOT a CAS
|
|
896
|
+
* conflict: nobody else actually holds the ref (no other writer is racing,
|
|
897
|
+
* the previous one is simply dead), so it must never be misread as "someone
|
|
898
|
+
* else updated it first" — see `./lease.ts`'s `acquireLease`, which used to
|
|
899
|
+
* (before this fix) read this as "lease held by someone else" and quietly
|
|
900
|
+
* back off forever, since the dead process's lock file never goes away on
|
|
901
|
+
* its own.
|
|
902
|
+
*/
|
|
903
|
+
export class StaleLockError extends Error {
|
|
904
|
+
constructor(ref, lockPath, stderr) {
|
|
905
|
+
super(`ref "${ref}" has a stale lock file at ${lockPath} — a previous git process ` +
|
|
906
|
+
`(e.g. a killed \`chant operator\`) was interrupted mid-write and left it ` +
|
|
907
|
+
`behind. Fix: remove it (\`rm ${lockPath}\`) and retry. git stderr: ${stderr.trim()}`);
|
|
908
|
+
this.ref = ref;
|
|
909
|
+
this.lockPath = lockPath;
|
|
910
|
+
this.name = "StaleLockError";
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/** Matches git's "Unable to create '<path>.lock': File exists." — the one stable, version-independent phrase every `git update-ref`/`git commit` etc. failure due to a leftover lock file uses, regardless of which ref or which git version. */
|
|
914
|
+
const STALE_LOCK_RE = /Unable to create '([^']+)':\s*File exists/;
|
|
915
|
+
/** Read the SHA `ref` currently points to, or `null` if it doesn't exist. Works for any ref, not just the lifecycle branch. */
|
|
916
|
+
export async function readRefSha(ref, opts) {
|
|
917
|
+
const rt = getRuntime();
|
|
918
|
+
const result = await rt.spawn(["git", "rev-parse", "--verify", ref], { cwd: opts?.cwd });
|
|
919
|
+
if (result.exitCode !== 0)
|
|
920
|
+
return null;
|
|
921
|
+
return result.stdout.trim() || null;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Turn a failed `git update-ref`'s stderr into the right error type (#1959
|
|
925
|
+
* finding 2). Real git collapses several distinct failure modes into the
|
|
926
|
+
* same nonzero exit code — a genuine CAS mismatch, a stale lock file left by
|
|
927
|
+
* a killed process, and an outright bad ref name all just say "update_ref
|
|
928
|
+
* failed" — so this doesn't trust the exit code alone:
|
|
929
|
+
*
|
|
930
|
+
* 1. A stale `.lock` file has a stable, distinctive message (see
|
|
931
|
+
* `STALE_LOCK_RE`) — checked first and reported as its own
|
|
932
|
+
* {@link StaleLockError}, never as a conflict.
|
|
933
|
+
* 2. Otherwise, re-read the ref's actual current value and compare it to
|
|
934
|
+
* `oldValue`: if it genuinely differs, this is a real CAS conflict —
|
|
935
|
+
* {@link RefCASConflictError}. This is the authoritative check (not
|
|
936
|
+
* stderr text matching) so it holds across git versions/locales.
|
|
937
|
+
* 3. If the ref's actual value still matches `oldValue` — the CAS itself
|
|
938
|
+
* should have succeeded — the failure is something else entirely (a bad
|
|
939
|
+
* ref name, a permissions problem, disk full, ...) and is surfaced as a
|
|
940
|
+
* plain `Error`, not miscategorized as either of the above.
|
|
941
|
+
*/
|
|
942
|
+
async function classifyRefFailure(ref, oldValue, stderr, opts) {
|
|
943
|
+
const lockMatch = stderr.match(STALE_LOCK_RE);
|
|
944
|
+
if (lockMatch) {
|
|
945
|
+
return new StaleLockError(ref, lockMatch[1], stderr);
|
|
946
|
+
}
|
|
947
|
+
const actual = await readRefSha(ref, opts);
|
|
948
|
+
if (actual !== oldValue) {
|
|
949
|
+
return new RefCASConflictError(ref, oldValue, stderr);
|
|
950
|
+
}
|
|
951
|
+
return new Error(`git update-ref failed for ref "${ref}": ${stderr.trim()}`);
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Compare-and-swap update of an arbitrary ref. `oldValue` is the SHA the
|
|
955
|
+
* caller last observed the ref at, or `null` to assert the ref does not yet
|
|
956
|
+
* exist (git's own convention: an empty `<oldvalue>` argument to
|
|
957
|
+
* `update-ref` means "must not exist"). Throws {@link RefCASConflictError}
|
|
958
|
+
* when the ref genuinely moved since `oldValue` was read, {@link
|
|
959
|
+
* StaleLockError} when a leftover lock file from a killed process is
|
|
960
|
+
* blocking the write, or a plain `Error` for anything else — never silently
|
|
961
|
+
* overwrites, and never misclassifies one failure as another (#1959 finding
|
|
962
|
+
* 2; see {@link classifyRefFailure}).
|
|
963
|
+
*/
|
|
964
|
+
export async function updateRefCAS(ref, newValue, oldValue, opts) {
|
|
965
|
+
const rt = getRuntime();
|
|
966
|
+
const result = await rt.spawn(["git", "update-ref", ref, newValue, oldValue ?? ""], { cwd: opts?.cwd });
|
|
967
|
+
if (result.exitCode !== 0) {
|
|
968
|
+
throw await classifyRefFailure(ref, oldValue, result.stderr ?? "", opts);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Compare-and-swap delete of an arbitrary ref — `oldValue` is required (no
|
|
973
|
+
* "delete unconditionally" escape hatch here) so releasing a lease you no
|
|
974
|
+
* longer hold can never delete someone else's newer one. Same failure
|
|
975
|
+
* classification as {@link updateRefCAS} (#1959 finding 2).
|
|
976
|
+
*/
|
|
977
|
+
export async function deleteRefCAS(ref, oldValue, opts) {
|
|
978
|
+
const rt = getRuntime();
|
|
979
|
+
const result = await rt.spawn(["git", "update-ref", "-d", ref, oldValue], { cwd: opts?.cwd });
|
|
980
|
+
if (result.exitCode !== 0) {
|
|
981
|
+
throw await classifyRefFailure(ref, oldValue, result.stderr ?? "", opts);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Write arbitrary content as a git blob object — no tree, no commit, no ref
|
|
986
|
+
* update. The building block a CAS ref's value can point at directly: a
|
|
987
|
+
* lease record (`./lease.ts`) has no meaningful "tree of files", so its ref
|
|
988
|
+
* targets a blob SHA rather than a commit the way `writeBlobToPath`'s tree-
|
|
989
|
+
* building pipeline does.
|
|
990
|
+
*/
|
|
991
|
+
export async function writeBlob(content, opts) {
|
|
992
|
+
const rt = getRuntime();
|
|
993
|
+
const result = await rt.spawn(["git", "hash-object", "-w", "--stdin"], { cwd: opts?.cwd, stdin: content });
|
|
994
|
+
if (result.exitCode !== 0)
|
|
995
|
+
throw new Error(`git hash-object failed: ${result.stderr}`);
|
|
996
|
+
return result.stdout.trim();
|
|
997
|
+
}
|
|
998
|
+
/** Read a blob's raw content by its SHA (whatever object a ref points at directly). Returns `null` when the object doesn't exist locally. */
|
|
999
|
+
export async function readBlobBySha(sha, opts) {
|
|
1000
|
+
const rt = getRuntime();
|
|
1001
|
+
const result = await rt.spawn(["git", "cat-file", "blob", sha], { cwd: opts?.cwd });
|
|
1002
|
+
if (result.exitCode !== 0)
|
|
1003
|
+
return null;
|
|
1004
|
+
return result.stdout;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Push one arbitrary ref (e.g. a lease ref) to the remote, guarded the same
|
|
1008
|
+
* way {@link pushLifecycle} guards the ledger branch: `--force-with-lease`
|
|
1009
|
+
* keyed to the remote SHA last observed locally, so a concurrent push from a
|
|
1010
|
+
* second machine is rejected rather than silently clobbered. The lease alone
|
|
1011
|
+
* lets a push replace a value that is not an ancestor — a lease ref's value is
|
|
1012
|
+
* a bare blob SHA, not a commit descending from the previous one — as long as
|
|
1013
|
+
* the remote still holds the expected value. No `--force` beside it: git lets
|
|
1014
|
+
* `--force` override `--force-with-lease`, which made every lease push
|
|
1015
|
+
* unconditional before #2732.
|
|
1016
|
+
*
|
|
1017
|
+
* Returns `false` (never throws) when no remote is configured — a
|
|
1018
|
+
* remote-less project's lease is local-only by construction (see
|
|
1019
|
+
* `./lease.ts`'s module doc), or when the push itself is rejected (the
|
|
1020
|
+
* caller re-reads and retries; see `acquireLease`). {@link pushRefStatus}
|
|
1021
|
+
* tells those two apart.
|
|
1022
|
+
*/
|
|
1023
|
+
export async function pushRef(ref, opts) {
|
|
1024
|
+
return (await pushRefStatus(ref, opts)) === "pushed";
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* {@link pushRef}, saying why a push did not land (#2732).
|
|
1028
|
+
*
|
|
1029
|
+
* `opts.expect` is the value the caller last saw the ref at on the remote
|
|
1030
|
+
* (`null`: absent there), and becomes the `--force-with-lease` expectation.
|
|
1031
|
+
* Without it the expectation is `refs/remotes/<remote>/<ref>`, which nothing
|
|
1032
|
+
* fetches a lease ref into, so it reads as "absent" and only a first push
|
|
1033
|
+
* lands. A lease passes the value of its own remote-tracking ref instead
|
|
1034
|
+
* (`./lease.ts`'s `readLease`).
|
|
1035
|
+
*
|
|
1036
|
+
* A ref that no longer exists locally is pushed as a deletion, under the same
|
|
1037
|
+
* expectation, so a released lease is released on the remote too. Deleting a
|
|
1038
|
+
* ref the caller expects to be absent already is a no-op that reports
|
|
1039
|
+
* `pushed`.
|
|
1040
|
+
*/
|
|
1041
|
+
export async function pushRefStatus(ref, opts) {
|
|
1042
|
+
const rt = getRuntime();
|
|
1043
|
+
const remoteResult = await rt.spawn(["git", "remote"], { cwd: opts?.cwd });
|
|
1044
|
+
if (remoteResult.exitCode !== 0 || !remoteResult.stdout.trim())
|
|
1045
|
+
return "no-remote";
|
|
1046
|
+
const remote = remoteResult.stdout.trim().split("\n")[0];
|
|
1047
|
+
let expected;
|
|
1048
|
+
if (opts?.expect !== undefined) {
|
|
1049
|
+
expected = opts.expect;
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
const remoteRef = `refs/remotes/${remote}/${ref.replace(/^refs\//, "")}`;
|
|
1053
|
+
const expectedResult = await rt.spawn(["git", "rev-parse", "--verify", remoteRef], { cwd: opts?.cwd });
|
|
1054
|
+
expected = expectedResult.exitCode === 0 ? expectedResult.stdout.trim() : null;
|
|
1055
|
+
}
|
|
1056
|
+
const lease = `${ref}:${expected ?? ""}`;
|
|
1057
|
+
const local = await readRefSha(ref, opts);
|
|
1058
|
+
if (local === null && expected === null)
|
|
1059
|
+
return "pushed";
|
|
1060
|
+
const refspec = local === null ? `:${ref}` : `${ref}:${ref}`;
|
|
1061
|
+
const pushResult = await rt.spawn(["git", "push", `--force-with-lease=${lease}`, remote, refspec], { cwd: opts?.cwd });
|
|
1062
|
+
return pushResult.exitCode === 0 ? "pushed" : "rejected";
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Fetch one arbitrary remote ref into a local ref of a possibly *different*
|
|
1066
|
+
* name (#1959 finding 3). `+` forces the update even when it isn't a
|
|
1067
|
+
* fast-forward (a lease ref's new value is rarely a descendant of its old
|
|
1068
|
+
* one). Returns `false` (never throws) when no remote is configured.
|
|
1069
|
+
*
|
|
1070
|
+
* The `localRef !== remoteRef` shape exists so a read path can observe what
|
|
1071
|
+
* the remote currently holds without ever touching a local ref another code
|
|
1072
|
+
* path treats as CAS-authoritative — see {@link fetchRef}'s doc and
|
|
1073
|
+
* `./lease.ts`'s `readLease`, which fetches into a side tracking ref
|
|
1074
|
+
* (`refs/chant/lease-remote/<op>`) for exactly this reason.
|
|
1075
|
+
*/
|
|
1076
|
+
export async function fetchRefInto(remoteRef, localRef, opts) {
|
|
1077
|
+
return (await fetchRefIntoStatus(remoteRef, localRef, opts)) === "fetched";
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* {@link fetchRefInto}, saying why nothing was fetched (#2732). `missing`
|
|
1081
|
+
* means the remote answered and does not carry the ref, which for a lease is
|
|
1082
|
+
* the news that it was released there.
|
|
1083
|
+
*/
|
|
1084
|
+
export async function fetchRefIntoStatus(remoteRef, localRef, opts) {
|
|
1085
|
+
const rt = getRuntime();
|
|
1086
|
+
const remoteResult = await rt.spawn(["git", "remote"], { cwd: opts?.cwd });
|
|
1087
|
+
if (remoteResult.exitCode !== 0 || !remoteResult.stdout.trim())
|
|
1088
|
+
return "no-remote";
|
|
1089
|
+
const remote = remoteResult.stdout.trim().split("\n")[0];
|
|
1090
|
+
const fetchResult = await rt.spawn(["git", "fetch", remote, `+${remoteRef}:${localRef}`], { cwd: opts?.cwd, env: C_LOCALE_ENV });
|
|
1091
|
+
if (fetchResult.exitCode === 0)
|
|
1092
|
+
return "fetched";
|
|
1093
|
+
return NO_SUCH_REMOTE_REF_RE.test(fetchResult.stderr ?? "") ? "missing" : "failed";
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Fetch one arbitrary ref from remote into the same local ref name.
|
|
1097
|
+
*
|
|
1098
|
+
* **Caution for a read path (#1959 finding 3):** this force-overwrites
|
|
1099
|
+
* `ref` locally (`+ref:ref`) — safe for a ref only a CAS write path ever
|
|
1100
|
+
* mutates locally between fetches, but NOT safe to call from a plain read
|
|
1101
|
+
* before every read if some other local writer (in the same clone) might be
|
|
1102
|
+
* mid-write: fetching here would force the local ref back to whatever the
|
|
1103
|
+
* remote last had, clobbering a just-written, not-yet-pushed local value out
|
|
1104
|
+
* from under it. `./lease.ts`'s `readLease` used to do exactly that; it now
|
|
1105
|
+
* uses {@link fetchRefInto} against a side tracking ref instead. Prefer
|
|
1106
|
+
* `fetchRefInto` for any new read-before-decide path.
|
|
1107
|
+
*/
|
|
1108
|
+
export async function fetchRef(ref, opts) {
|
|
1109
|
+
return fetchRefInto(ref, ref, opts);
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Get the current HEAD commit SHA of the main working branch.
|
|
1113
|
+
*/
|
|
1114
|
+
export async function getHeadCommit(opts) {
|
|
1115
|
+
const rt = getRuntime();
|
|
1116
|
+
const result = await rt.spawn(["git", "rev-parse", "HEAD"], { cwd: opts?.cwd });
|
|
1117
|
+
if (result.exitCode !== 0) {
|
|
1118
|
+
throw new Error(`git rev-parse HEAD failed: ${result.stderr}`);
|
|
1119
|
+
}
|
|
1120
|
+
return result.stdout.trim();
|
|
1121
|
+
}
|
|
1122
|
+
async function getStateBranchTip(cwd) {
|
|
1123
|
+
const rt = getRuntime();
|
|
1124
|
+
const result = await rt.spawn(["git", "rev-parse", "--verify", `refs/heads/${STATE_BRANCH}`], { cwd });
|
|
1125
|
+
if (result.exitCode !== 0)
|
|
1126
|
+
return null;
|
|
1127
|
+
return result.stdout.trim();
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* The entries of directory `dir` (branch-relative, `""` for the root) in
|
|
1131
|
+
* commit `tip`, or an empty map when it does not exist. Always pinned to the
|
|
1132
|
+
* resolved `tip` sha, never to `STATE_BRANCH`: a write reads several
|
|
1133
|
+
* directories, and a branch name re-resolves on each read, so a concurrent
|
|
1134
|
+
* commit would splice entries from two commits into one tree undetectably.
|
|
1135
|
+
*
|
|
1136
|
+
* `--full-tree` (#2550): run from a project in a subdirectory of the
|
|
1137
|
+
* repository, a bare `ls-tree` lists only the part of the tree under that
|
|
1138
|
+
* subdirectory, which on the orphan branch is nothing. The write then
|
|
1139
|
+
* rebuilt the branch from an empty tree, and an append's CAS refused it as a
|
|
1140
|
+
* concurrent change.
|
|
1141
|
+
*/
|
|
1142
|
+
async function listTreeAt(tip, dir, cwd) {
|
|
1143
|
+
const rt = getRuntime();
|
|
1144
|
+
const entries = new Map();
|
|
1145
|
+
const result = await rt.spawn(["git", "ls-tree", "--full-tree", dir === "" ? tip : `${tip}:${dir}/`], { cwd });
|
|
1146
|
+
if (result.exitCode !== 0)
|
|
1147
|
+
return entries;
|
|
1148
|
+
for (const line of result.stdout.split("\n")) {
|
|
1149
|
+
// Format: mode type sha\tname
|
|
1150
|
+
const match = line.match(/^(\d+)\s+(\w+)\s+([0-9a-f]+)\t(.+)$/);
|
|
1151
|
+
if (match)
|
|
1152
|
+
entries.set(match[4], { mode: match[1], type: match[2], sha: match[3] });
|
|
1153
|
+
}
|
|
1154
|
+
return entries;
|
|
1155
|
+
}
|
|
1156
|
+
function splitPath(path) {
|
|
1157
|
+
const slash = path.lastIndexOf("/");
|
|
1158
|
+
return { dir: path.slice(0, slash), name: path.slice(slash + 1) };
|
|
1159
|
+
}
|
|
1160
|
+
function parentDir(dir) {
|
|
1161
|
+
const slash = dir.lastIndexOf("/");
|
|
1162
|
+
return slash < 0 ? "" : dir.slice(0, slash);
|
|
1163
|
+
}
|
|
1164
|
+
function baseName(dir) {
|
|
1165
|
+
return dir.slice(dir.lastIndexOf("/") + 1);
|
|
1166
|
+
}
|
|
1167
|
+
function depth(dir) {
|
|
1168
|
+
return dir === "" ? 0 : dir.split("/").length;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* The branch's read side as a {@link LedgerReadPort} for the member `opts`
|
|
1172
|
+
* names (#3865): what the bundle-safe readers (`./release-read.ts`,
|
|
1173
|
+
* `./wave-plans.ts`) are handed. A product with its own way to read the
|
|
1174
|
+
* branch passes its own port instead and never loads this module.
|
|
1175
|
+
*/
|
|
1176
|
+
export function gitLedgerReadPort(opts) {
|
|
1177
|
+
return {
|
|
1178
|
+
read: (dir, file) => readBlobFromPath(dir, file, opts),
|
|
1179
|
+
list: (dir) => listFilesInDir(dir, opts),
|
|
1180
|
+
trees: () => listLedgerTrees(opts),
|
|
1181
|
+
};
|
|
1182
|
+
}
|