@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.13
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/CHANGELOG.md +5992 -82
- package/COMPARISON.md +168 -63
- package/CONTRIBUTING.md +121 -0
- package/MIGRATING-from-OpenSpec.md +238 -55
- package/QUICKSTART.md +302 -0
- package/README.md +653 -144
- package/ROADMAP.md +1035 -0
- package/SCHEMA.md +3916 -263
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +927 -0
- package/dist/cli.js +133 -21
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +204 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +309 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +74 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +172 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +193 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +319 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +298 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +106 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +318 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +30 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +355 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +166 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +305 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +367 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +221 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +6 -8
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +86 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +65 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +227 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +39 -28
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +152 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +66 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +105 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +262 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +146 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +72 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +142 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +63 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +155 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +358 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +344 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +108 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +238 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +75 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +175 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +143 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +86 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +18 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +162 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +40 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +62 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +328 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +279 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +199 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +38 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +300 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +221 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +278 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +174 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +182 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +53 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/show/service.js +239 -0
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +87 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +47 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +209 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +115 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +269 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +325 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +113 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +98 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +318 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +196 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +249 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +176 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/unarchive/restore.js +254 -0
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +59 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +245 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +204 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +58 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +264 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +85 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +222 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +93 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +371 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +118 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +266 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +71 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +162 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +75 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +52 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +89 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +145 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +252 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +241 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +177 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +310 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +279 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +148 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +363 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +178 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +81 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +263 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +278 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +206 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +328 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +220 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +266 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +141 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +230 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +111 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +155 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +169 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +299 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +46 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +50 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +59 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +65 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +44 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +78 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +104 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +138 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +273 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md.js +203 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +60 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +254 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +144 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +151 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +193 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +280 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +373 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +323 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +220 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +188 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +311 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +67 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +210 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +19 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +338 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +43 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +146 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +92 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +38 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +290 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +195 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +83 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +263 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +285 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +385 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +273 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +304 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +58 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +190 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +243 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +110 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +113 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +161 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +89 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +219 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +174 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +173 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +369 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +308 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +167 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +30 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +73 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +337 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +298 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +166 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +47 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +338 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +174 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +123 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +205 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +104 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +267 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +185 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +75 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +332 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.js.map +1 -0
- package/dist/core/coherence/declared.js +295 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +191 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +335 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +189 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +63 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +95 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +341 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +230 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +237 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +204 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +312 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/docs.js +37 -85
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +238 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +139 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +355 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +224 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +103 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +286 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +238 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +234 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +89 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +209 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +180 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +194 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +103 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/explore/explore.js +204 -0
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +174 -146
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +208 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +246 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +176 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +210 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +109 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +73 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/{concurrency.js → kernel/concurrency.js} +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +56 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/{records.js → kernel/records.js} +11 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +66 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +118 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +265 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +212 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +73 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +324 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +207 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +142 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +174 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +301 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +136 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +239 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +54 -108
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +199 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +16 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +204 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +232 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +110 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +220 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +78 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +70 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +241 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +191 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +304 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +116 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +157 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +120 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- package/dist/core/provenance/findings.js +244 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +201 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +139 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +280 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +220 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +210 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +322 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +308 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +61 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +90 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +255 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +146 -14
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +239 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +252 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +268 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +95 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +204 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/snapshot/snapshot.js +196 -0
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +181 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +60 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +126 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +214 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +163 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +216 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +302 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +216 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/tasks/packet.js +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +166 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +140 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +101 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +76 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +296 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/grade/capability-tag.js +93 -0
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/core/usecases/grade/requirement-tag.js +94 -0
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/core/usecases/grade/steps.js +266 -0
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/core/usecases/grade/usecases.js +112 -0
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +123 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +180 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +170 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +259 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +158 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/read/shape.js +169 -0
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +165 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +99 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +61 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +59 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +216 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/vocabulary/report.js +90 -0
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +232 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +336 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +18 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +74 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +132 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +31 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +73 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +8 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +92 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +134 -0
- package/examples/docs/services/order-service/asyncapi.yaml +109 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +126 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +76 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +68 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +120 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +81 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +200 -0
- package/examples/docs/services/platform/notification-service/health.yaml +40 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +97 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js +0 -320
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js +0 -317
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js +0 -253
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js +0 -225
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js +0 -47
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The undo snapshot: the bytes `archive` is about to overwrite, parked inside
|
|
3
|
+
* the feature directory so they travel with it into `features/archive/`.
|
|
4
|
+
*
|
|
5
|
+
* Separate from the commit because the merge is not invertible — a MODIFIED
|
|
6
|
+
* requirement's previous text appears nowhere in the delta, and a landscape
|
|
7
|
+
* rewritten by hand since cannot be un-rewritten by re-reading one. `unarchive`
|
|
8
|
+
* puts bytes back; it does not recompute them. Nothing on the commit path reads
|
|
9
|
+
* this module: only `archive` writes a snapshot and only `unarchive` honours one.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { repoPath } from "../../envelope/json.js";
|
|
15
|
+
import { acrossRename, resolvePortableFileInsideLexically, UnsafePathError } from "../../kernel/path-safety.js";
|
|
16
|
+
// Every `isRecord` below guards the manifest loam itself wrote, read back inside
|
|
17
|
+
// a REFUSAL path — `../lock.ts` records why that is where a thrown `TypeError`
|
|
18
|
+
// costs the most.
|
|
19
|
+
import { isRecord } from "../../kernel/records.js";
|
|
20
|
+
import { isDigest, sha256 } from "../writes.js";
|
|
21
|
+
import { message, quietRm } from "../commit.js";
|
|
22
|
+
import { isReadableSnapshotVersion, preImageRel, SNAPSHOT_DIR, SNAPSHOT_MANIFEST, SNAPSHOT_VERSION, snapshotDir, } from "./manifest.js";
|
|
23
|
+
/** A snapshot directory that must not be replaced, because it is the only copy of the true pre-images. */
|
|
24
|
+
export class SnapshotClobberError extends Error {
|
|
25
|
+
dir;
|
|
26
|
+
name = "SnapshotClobberError";
|
|
27
|
+
constructor(dir, detail) {
|
|
28
|
+
super(`${dir} already holds an undo snapshot that does NOT match the living docs (${detail}) — a previous archive of ` +
|
|
29
|
+
`this feature wrote to the living docs and never took them back, and these are the only copies of what they ` +
|
|
30
|
+
`said before. Overwriting them would make that merge permanent and unreportable. Run \`loam archive\` again ` +
|
|
31
|
+
`after restoring the living docs from version control, or delete ${SNAPSHOT_DIR}/ if you know the merge never landed.`);
|
|
32
|
+
this.dir = dir;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function writeSnapshot(request) {
|
|
36
|
+
const { featureDir, docsDir, staged } = request;
|
|
37
|
+
const { featureId, dirName } = request.feature;
|
|
38
|
+
const dir = snapshotDir(featureDir);
|
|
39
|
+
await assertSnapshotReplaceable(request);
|
|
40
|
+
// A leftover from a rolled-back run would describe a merge that never happened.
|
|
41
|
+
await quietRm(dir);
|
|
42
|
+
const files = [];
|
|
43
|
+
for (const s of staged) {
|
|
44
|
+
// The feature's own documents by where the move leaves them; everything
|
|
45
|
+
// else by where it is written (`SnapshotRequest.movesTo` says why).
|
|
46
|
+
const rel = repoPath(docsDir, acrossRename(s.write.path, featureDir, request.movesTo));
|
|
47
|
+
// services/ entries are re-keyed by (service, artifact) so they survive the
|
|
48
|
+
// service directory moving between archive and unarchive; the literal path
|
|
49
|
+
// is still recorded — it keys the pre-image below, and it is the fallback.
|
|
50
|
+
const key = request.serviceKeyOf(rel);
|
|
51
|
+
files.push({
|
|
52
|
+
path: rel,
|
|
53
|
+
...key,
|
|
54
|
+
existed: s.before !== null,
|
|
55
|
+
after: sha256(s.content ?? Buffer.alloc(0)),
|
|
56
|
+
before: s.before === null ? null : sha256(s.before),
|
|
57
|
+
});
|
|
58
|
+
if (s.before !== null) {
|
|
59
|
+
// Under the suffixed name (`./manifest.ts`, version 4): a pre-image kept
|
|
60
|
+
// under its living name is a second living document to every tool that
|
|
61
|
+
// reads by extension.
|
|
62
|
+
const dest = join(dir, "files", preImageRel(SNAPSHOT_VERSION, rel));
|
|
63
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
64
|
+
await writeFile(dest, s.before);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const manifest = {
|
|
68
|
+
version: SNAPSHOT_VERSION,
|
|
69
|
+
feature: featureId,
|
|
70
|
+
dirName,
|
|
71
|
+
archivedAt: new Date().toISOString(),
|
|
72
|
+
...(request.checklist === undefined ? {} : { checklist: request.checklist }),
|
|
73
|
+
files,
|
|
74
|
+
};
|
|
75
|
+
// Manifest last: its presence is what says the pre-images beside it are complete.
|
|
76
|
+
await mkdir(dir, { recursive: true });
|
|
77
|
+
await writeFile(join(dir, SNAPSHOT_MANIFEST), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Refuse to replace a snapshot whose pre-images are still the only record of
|
|
81
|
+
* what the living docs said.
|
|
82
|
+
*
|
|
83
|
+
* A snapshot in an ACTIVE feature directory means a previous archive of this
|
|
84
|
+
* same feature wrote it — and since a clean rollback deletes it, finding one
|
|
85
|
+
* means that run either died mid-commit or reported `rollback-incomplete`. If
|
|
86
|
+
* its pre-images still match the living docs, nothing of that run landed and
|
|
87
|
+
* the snapshot is stale bookkeeping. If they do not, the living docs carry
|
|
88
|
+
* that run's writes, these files are the only way back, and the unconditional
|
|
89
|
+
* clear turned a repairable half-merge into a permanent one — after which
|
|
90
|
+
* `unarchive` restored the half-merged text and said the docs were back.
|
|
91
|
+
*
|
|
92
|
+
* Version 2 and 3 manifests are graded by the same staleness question — version
|
|
93
|
+
* 2 over its literal paths — because refusing them as "a different loam" would
|
|
94
|
+
* turn every leftover snapshot an older loam wrote into a permanent archive
|
|
95
|
+
* refusal on upgrade. The question reads only the manifest and the living docs,
|
|
96
|
+
* never a pre-image, so where each layout stores its pre-images does not enter
|
|
97
|
+
* into it.
|
|
98
|
+
*/
|
|
99
|
+
async function assertSnapshotReplaceable(request) {
|
|
100
|
+
const { docsDir, serviceDirOf } = request;
|
|
101
|
+
const dir = snapshotDir(request.featureDir);
|
|
102
|
+
const manifestPath = join(dir, SNAPSHOT_MANIFEST);
|
|
103
|
+
// The manifest is written LAST, so a `files/` tree without one is a snapshot
|
|
104
|
+
// whose run died before it could swap anything: it describes no merge.
|
|
105
|
+
if (!existsSync(manifestPath))
|
|
106
|
+
return;
|
|
107
|
+
let manifest;
|
|
108
|
+
try {
|
|
109
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);
|
|
113
|
+
}
|
|
114
|
+
// The cast that used to stand here asserted the shape rather than checking
|
|
115
|
+
// it: well-formed JSON that is not an object answered `undefined` to every
|
|
116
|
+
// field below and slipped through as "a different loam", and a bare `null`
|
|
117
|
+
// threw a TypeError out of the refusal itself. Both left this directory —
|
|
118
|
+
// which may hold the only copies of what the living docs said — one step from
|
|
119
|
+
// being replaced, without the staleness question ever having been asked.
|
|
120
|
+
if (!isRecord(manifest)) {
|
|
121
|
+
throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);
|
|
122
|
+
}
|
|
123
|
+
if (!isReadableSnapshotVersion(manifest.version) || !Array.isArray(manifest.files)) {
|
|
124
|
+
throw new SnapshotClobberError(dir, `it was written by a different loam and cannot be checked against the living docs`);
|
|
125
|
+
}
|
|
126
|
+
// `Array.isArray` narrows to `any[]`; naming the element type here is what
|
|
127
|
+
// keeps that `any` from spreading past the one row being read.
|
|
128
|
+
const rows = manifest.files;
|
|
129
|
+
const landed = [];
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
const entry = readSnapshotClaim(row);
|
|
132
|
+
if (entry === null) {
|
|
133
|
+
throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} is malformed, so nothing can say whether it is stale`);
|
|
134
|
+
}
|
|
135
|
+
let target;
|
|
136
|
+
try {
|
|
137
|
+
// These paths are data, and they are the ones this function goes and
|
|
138
|
+
// reads. They name living documents, so containment is lexical: a docs
|
|
139
|
+
// repo may legitimately mount a service directory through a symlink, and
|
|
140
|
+
// `unarchive` resolves the same field the same way before it writes back.
|
|
141
|
+
target = resolvePortableFileInsideLexically(docsDir, entry.path, `snapshot path '${entry.path}'`);
|
|
142
|
+
// A re-keyed entry's CURRENT location is wherever the enumeration says
|
|
143
|
+
// its service lives today — grading staleness at the old address would
|
|
144
|
+
// call a merely-moved file a landed merge. The literal path was still
|
|
145
|
+
// resolved above on purpose: its containment is part of what "loam wrote
|
|
146
|
+
// this manifest" means, and it stays the pre-image's key. An id the
|
|
147
|
+
// enumeration no longer answers falls back to that literal path.
|
|
148
|
+
if (entry.service !== undefined && entry.artifact !== undefined) {
|
|
149
|
+
const current = serviceDirOf(entry.service);
|
|
150
|
+
if (current !== null) {
|
|
151
|
+
target = resolvePortableFileInsideLexically(docsDir, `${current}/${entry.artifact}`, `snapshot path '${entry.path}'`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (!(err instanceof UnsafePathError))
|
|
157
|
+
throw err;
|
|
158
|
+
throw new SnapshotClobberError(dir, `${message(err)}, so nothing can say whether it is stale`);
|
|
159
|
+
}
|
|
160
|
+
// A row of the feature's own documents names where the move WOULD have
|
|
161
|
+
// left it, and a snapshot found here is one whose feature never moved: the
|
|
162
|
+
// bytes to grade are still under this feature directory. Graded at the
|
|
163
|
+
// literal path instead, every such row read as absent — a landed merge —
|
|
164
|
+
// and an archive that a crash or a rollback interrupted could never be
|
|
165
|
+
// retried over its own leftover. Named where it was graded, too: the
|
|
166
|
+
// refusal sends a person to restore the file from version control, and
|
|
167
|
+
// the archived path names a directory that does not exist yet.
|
|
168
|
+
const graded = acrossRename(target, request.movesTo, request.featureDir);
|
|
169
|
+
const now = existsSync(graded) ? sha256(await readFile(graded)) : null;
|
|
170
|
+
// `existed: false` means archive created the file, so "nothing landed"
|
|
171
|
+
// means it is still absent.
|
|
172
|
+
const untouched = entry.existed ? now === entry.before : now === null;
|
|
173
|
+
if (!untouched)
|
|
174
|
+
landed.push(graded === target ? entry.path : repoPath(docsDir, graded));
|
|
175
|
+
}
|
|
176
|
+
if (landed.length > 0)
|
|
177
|
+
throw new SnapshotClobberError(dir, `${landed.length} file(s) already carry that merge: ${landed.join(", ")}`);
|
|
178
|
+
}
|
|
179
|
+
function readSnapshotClaim(value) {
|
|
180
|
+
if (!isRecord(value))
|
|
181
|
+
return null;
|
|
182
|
+
const { path, existed, before, service, artifact } = value;
|
|
183
|
+
if (typeof path !== "string" || typeof existed !== "boolean")
|
|
184
|
+
return null;
|
|
185
|
+
let key;
|
|
186
|
+
if (service === undefined && artifact === undefined)
|
|
187
|
+
key = {};
|
|
188
|
+
else if (typeof service === "string" && typeof artifact === "string")
|
|
189
|
+
key = { service, artifact };
|
|
190
|
+
else
|
|
191
|
+
return null;
|
|
192
|
+
if (existed)
|
|
193
|
+
return isDigest(before) ? { path, existed, before, ...key } : null;
|
|
194
|
+
return before === null ? { path, existed, before, ...key } : null;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../../src/core/staging/snapshot/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The undo snapshot: the bytes `archive` is about to overwrite, parked inside\n * the feature directory so they travel with it into `features/archive/`.\n *\n * Separate from the commit because the merge is not invertible — a MODIFIED\n * requirement's previous text appears nowhere in the delta, and a landscape\n * rewritten by hand since cannot be un-rewritten by re-reading one. `unarchive`\n * puts bytes back; it does not recompute them. Nothing on the commit path reads\n * this module: only `archive` writes a snapshot and only `unarchive` honours one.\n */\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { acrossRename, resolvePortableFileInsideLexically, UnsafePathError } from \"../../kernel/path-safety.js\";\n// Every `isRecord` below guards the manifest loam itself wrote, read back inside\n// a REFUSAL path — `../lock.ts` records why that is where a thrown `TypeError`\n// costs the most.\nimport { isRecord } from \"../../kernel/records.js\";\nimport { isDigest, sha256, type StagedWrite } from \"../writes.js\";\nimport { message, quietRm } from \"../commit.js\";\nimport {\n isReadableSnapshotVersion,\n preImageRel,\n SNAPSHOT_DIR,\n SNAPSHOT_MANIFEST,\n SNAPSHOT_VERSION,\n snapshotDir,\n type SnapshotEntry,\n type SnapshotManifest,\n} from \"./manifest.js\";\n\n/** A snapshot directory that must not be replaced, because it is the only copy of the true pre-images. */\nexport class SnapshotClobberError extends Error {\n override readonly name = \"SnapshotClobberError\";\n\n constructor(readonly dir: string, detail: string) {\n super(\n `${dir} already holds an undo snapshot that does NOT match the living docs (${detail}) — a previous archive of ` +\n `this feature wrote to the living docs and never took them back, and these are the only copies of what they ` +\n `said before. Overwriting them would make that merge permanent and unreportable. Run \\`loam archive\\` again ` +\n `after restoring the living docs from version control, or delete ${SNAPSHOT_DIR}/ if you know the merge never landed.`,\n );\n }\n}\n\n/** The (service, artifact) key a `services/` path decomposes into. */\nexport interface ServiceKey {\n service: string;\n artifact: string;\n}\n\n/**\n * Everything `writeSnapshot` needs, as one object. The resolvers are INJECTED\n * by the caller — `archive` builds them from its view of the tree — because\n * how a path decomposes into a service identity is repo-layout knowledge, and\n * this module's subject is parking and checking bytes.\n */\nexport interface SnapshotRequest {\n featureDir: string;\n /**\n * Where the archive moves `featureDir` — the directory this snapshot is\n * about to travel into. A staged write INSIDE `featureDir` is one of the\n * feature's own documents (the archive re-expresses their links for the\n * move), and it is recorded at its image under here: that is where its bytes\n * sit whenever an undo can be asked for, so `unarchive` restores it by its\n * literal path before moving the directory back, exactly as it restores\n * every other row. The other reader, the clobber guard below, meets a\n * snapshot BEFORE its feature moved and grades such a row at its image back\n * under `featureDir`.\n */\n movesTo: string;\n docsDir: string;\n feature: { featureId: string; dirName: string };\n staged: StagedWrite[];\n /** Docs-relative path → the service it belongs to; null for landscape/features paths. */\n serviceKeyOf: (rel: string) => ServiceKey | null;\n /** Service id → its CURRENT docs-relative directory; null when not enumerated. */\n serviceDirOf: (service: string) => string | null;\n /**\n * Recorded verbatim as the manifest's `checklist` when given. Opaque here —\n * this module parks bytes and never learns what a checklist is; `archive`\n * derives it and `gate --since` reads it back (`./manifest.ts`).\n */\n checklist?: string;\n}\n\nexport async function writeSnapshot(request: SnapshotRequest): Promise<void> {\n const { featureDir, docsDir, staged } = request;\n const { featureId, dirName } = request.feature;\n const dir = snapshotDir(featureDir);\n await assertSnapshotReplaceable(request);\n // A leftover from a rolled-back run would describe a merge that never happened.\n await quietRm(dir);\n\n const files: SnapshotEntry[] = [];\n for (const s of staged) {\n // The feature's own documents by where the move leaves them; everything\n // else by where it is written (`SnapshotRequest.movesTo` says why).\n const rel = repoPath(docsDir, acrossRename(s.write.path, featureDir, request.movesTo));\n // services/ entries are re-keyed by (service, artifact) so they survive the\n // service directory moving between archive and unarchive; the literal path\n // is still recorded — it keys the pre-image below, and it is the fallback.\n const key = request.serviceKeyOf(rel);\n files.push({\n path: rel,\n ...key,\n existed: s.before !== null,\n after: sha256(s.content ?? Buffer.alloc(0)),\n before: s.before === null ? null : sha256(s.before),\n });\n if (s.before !== null) {\n // Under the suffixed name (`./manifest.ts`, version 4): a pre-image kept\n // under its living name is a second living document to every tool that\n // reads by extension.\n const dest = join(dir, \"files\", preImageRel(SNAPSHOT_VERSION, rel));\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, s.before);\n }\n }\n\n const manifest: SnapshotManifest = {\n version: SNAPSHOT_VERSION,\n feature: featureId,\n dirName,\n archivedAt: new Date().toISOString(),\n ...(request.checklist === undefined ? {} : { checklist: request.checklist }),\n files,\n };\n // Manifest last: its presence is what says the pre-images beside it are complete.\n await mkdir(dir, { recursive: true });\n await writeFile(join(dir, SNAPSHOT_MANIFEST), JSON.stringify(manifest, null, 2) + \"\\n\", \"utf8\");\n}\n\n/**\n * Refuse to replace a snapshot whose pre-images are still the only record of\n * what the living docs said.\n *\n * A snapshot in an ACTIVE feature directory means a previous archive of this\n * same feature wrote it — and since a clean rollback deletes it, finding one\n * means that run either died mid-commit or reported `rollback-incomplete`. If\n * its pre-images still match the living docs, nothing of that run landed and\n * the snapshot is stale bookkeeping. If they do not, the living docs carry\n * that run's writes, these files are the only way back, and the unconditional\n * clear turned a repairable half-merge into a permanent one — after which\n * `unarchive` restored the half-merged text and said the docs were back.\n *\n * Version 2 and 3 manifests are graded by the same staleness question — version\n * 2 over its literal paths — because refusing them as \"a different loam\" would\n * turn every leftover snapshot an older loam wrote into a permanent archive\n * refusal on upgrade. The question reads only the manifest and the living docs,\n * never a pre-image, so where each layout stores its pre-images does not enter\n * into it.\n */\nasync function assertSnapshotReplaceable(request: SnapshotRequest): Promise<void> {\n const { docsDir, serviceDirOf } = request;\n const dir = snapshotDir(request.featureDir);\n const manifestPath = join(dir, SNAPSHOT_MANIFEST);\n // The manifest is written LAST, so a `files/` tree without one is a snapshot\n // whose run died before it could swap anything: it describes no merge.\n if (!existsSync(manifestPath)) return;\n let manifest: unknown;\n try {\n manifest = JSON.parse(await readFile(manifestPath, \"utf8\"));\n } catch {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);\n }\n // The cast that used to stand here asserted the shape rather than checking\n // it: well-formed JSON that is not an object answered `undefined` to every\n // field below and slipped through as \"a different loam\", and a bare `null`\n // threw a TypeError out of the refusal itself. Both left this directory —\n // which may hold the only copies of what the living docs said — one step from\n // being replaced, without the staleness question ever having been asked.\n if (!isRecord(manifest)) {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);\n }\n if (!isReadableSnapshotVersion(manifest.version) || !Array.isArray(manifest.files)) {\n throw new SnapshotClobberError(dir, `it was written by a different loam and cannot be checked against the living docs`);\n }\n // `Array.isArray` narrows to `any[]`; naming the element type here is what\n // keeps that `any` from spreading past the one row being read.\n const rows: unknown[] = manifest.files;\n const landed: string[] = [];\n for (const row of rows) {\n const entry = readSnapshotClaim(row);\n if (entry === null) {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} is malformed, so nothing can say whether it is stale`);\n }\n let target: string;\n try {\n // These paths are data, and they are the ones this function goes and\n // reads. They name living documents, so containment is lexical: a docs\n // repo may legitimately mount a service directory through a symlink, and\n // `unarchive` resolves the same field the same way before it writes back.\n target = resolvePortableFileInsideLexically(docsDir, entry.path, `snapshot path '${entry.path}'`);\n // A re-keyed entry's CURRENT location is wherever the enumeration says\n // its service lives today — grading staleness at the old address would\n // call a merely-moved file a landed merge. The literal path was still\n // resolved above on purpose: its containment is part of what \"loam wrote\n // this manifest\" means, and it stays the pre-image's key. An id the\n // enumeration no longer answers falls back to that literal path.\n if (entry.service !== undefined && entry.artifact !== undefined) {\n const current = serviceDirOf(entry.service);\n if (current !== null) {\n target = resolvePortableFileInsideLexically(\n docsDir,\n `${current}/${entry.artifact}`,\n `snapshot path '${entry.path}'`,\n );\n }\n }\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n throw new SnapshotClobberError(dir, `${message(err)}, so nothing can say whether it is stale`);\n }\n // A row of the feature's own documents names where the move WOULD have\n // left it, and a snapshot found here is one whose feature never moved: the\n // bytes to grade are still under this feature directory. Graded at the\n // literal path instead, every such row read as absent — a landed merge —\n // and an archive that a crash or a rollback interrupted could never be\n // retried over its own leftover. Named where it was graded, too: the\n // refusal sends a person to restore the file from version control, and\n // the archived path names a directory that does not exist yet.\n const graded = acrossRename(target, request.movesTo, request.featureDir);\n const now = existsSync(graded) ? sha256(await readFile(graded)) : null;\n // `existed: false` means archive created the file, so \"nothing landed\"\n // means it is still absent.\n const untouched = entry.existed ? now === entry.before : now === null;\n if (!untouched) landed.push(graded === target ? entry.path : repoPath(docsDir, graded));\n }\n if (landed.length > 0) throw new SnapshotClobberError(dir, `${landed.length} file(s) already carry that merge: ${landed.join(\", \")}`);\n}\n\n/**\n * One manifest row read back as the record `writeSnapshot` wrote, or null when\n * the file does not actually say.\n *\n * Every field the staleness question consumes is checked, because an unchecked\n * field does not read as missing here — it reads as an ANSWER. An `existed`\n * that is not a boolean is falsy, and a `false` claim is graded by asking\n * whether the file is ABSENT: a manifest recording a pre-image for a deleted\n * file therefore graded as stale bookkeeping and was cleared, taking the only\n * copy of that pre-image with it. A `before` of the wrong type equals no digest\n * at all, so the opposite row accused an untouched file of carrying a merge.\n *\n * `unarchive` asks a stricter version of the same question of the same file\n * (`after`, `archivedAt`, the feature name, no duplicates) because it is about\n * to write THROUGH it; that check stays where it is, with its own refusals.\n *\n * `service`/`artifact` are read when present (a keyed `services/` row) and\n * must arrive as a pair: half a key is a manifest that does not say where the\n * file lives, and grading through the literal path anyway would be a guess.\n */\ninterface SnapshotClaim {\n path: string;\n existed: boolean;\n before: string | null;\n service?: string;\n artifact?: string;\n}\n\nfunction readSnapshotClaim(value: unknown): SnapshotClaim | null {\n if (!isRecord(value)) return null;\n const { path, existed, before, service, artifact } = value;\n if (typeof path !== \"string\" || typeof existed !== \"boolean\") return null;\n let key: { service: string; artifact: string } | Record<string, never>;\n if (service === undefined && artifact === undefined) key = {};\n else if (typeof service === \"string\" && typeof artifact === \"string\") key = { service, artifact };\n else return null;\n if (existed) return isDigest(before) ? { path, existed, before, ...key } : null;\n return before === null ? { path, existed, before, ...key } : null;\n}\n\n"]}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roll a version-2 or version-3 journal FORWARD: finish the renames a killed
|
|
3
|
+
* commit did not reach, from the staged temps that are already durable on
|
|
4
|
+
* disk (and, for a version-3 record, the directory renames after them).
|
|
5
|
+
*
|
|
6
|
+
* Forward and not undo, because that is what is actually durable: the
|
|
7
|
+
* after-bytes were written and (per stageWrites) fully on disk before the
|
|
8
|
+
* journal existed, while the OVERWRITTEN files' pre-images are durable
|
|
9
|
+
* nowhere — the rename that replaced them destroyed the only copy. The plan
|
|
10
|
+
* was validated completely before the first rename, so the post-state is
|
|
11
|
+
* exactly what a successful run would have produced; unarchive's
|
|
12
|
+
* finish-never-undo recovery is the precedent.
|
|
13
|
+
*
|
|
14
|
+
* The refuse-to-choose doctrine is rollbackStaged's, verbatim: a file whose
|
|
15
|
+
* bytes are neither the before- nor the after-digest was edited by somebody
|
|
16
|
+
* since the crash, and completing over it would destroy one of two truths.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
19
|
+
import { readFile, rename, rm } from "node:fs/promises";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import { quietRmdir } from "../commit.js";
|
|
22
|
+
import { sha256 } from "../writes.js";
|
|
23
|
+
import { COMMIT_INTENT, InterruptedCommitError, sweepPidTemps } from "../interrupted.js";
|
|
24
|
+
import { clearTxnIntent } from "./journal.js";
|
|
25
|
+
export async function recoverForward(root, intent) {
|
|
26
|
+
const abs = (rel) => join(root, ...rel.split("/"));
|
|
27
|
+
// A directory rename is classified by pure existence — a rename moves the
|
|
28
|
+
// inode, so "which end exists" is exactly as discriminating as a digest is
|
|
29
|
+
// for a file. Both ends existing (or neither) is a state no step of the
|
|
30
|
+
// commit produces: somebody made a directory since the crash, and choosing
|
|
31
|
+
// which tree to keep is the refuse-to-choose case below.
|
|
32
|
+
const movesDone = [];
|
|
33
|
+
const movesPending = [];
|
|
34
|
+
const unknownMoves = [];
|
|
35
|
+
for (const m of intent.moves) {
|
|
36
|
+
const fromExists = existsSync(abs(m.from));
|
|
37
|
+
const toExists = existsSync(abs(m.to));
|
|
38
|
+
if (toExists && !fromExists)
|
|
39
|
+
movesDone.push(m);
|
|
40
|
+
else if (fromExists && !toExists)
|
|
41
|
+
movesPending.push(m);
|
|
42
|
+
else
|
|
43
|
+
unknownMoves.push(`${m.from} -> ${m.to}`);
|
|
44
|
+
}
|
|
45
|
+
// Where a recorded file IS now. The journal records a file at the path its
|
|
46
|
+
// swap wrote — and a `subsystem move` swaps the documents whose links it
|
|
47
|
+
// rewrites INSIDE a directory it then renames, so once that rename has
|
|
48
|
+
// landed the file is under the moved directory, not at the recorded path.
|
|
49
|
+
// Looked for there, it would read as absent: "edited since", a refusal over
|
|
50
|
+
// a commit that only needed its journal cleared. Classified moves first for
|
|
51
|
+
// exactly this; a file under a move still pending is where it was recorded.
|
|
52
|
+
const target = (f) => {
|
|
53
|
+
const landed = movesDone.find((m) => f.path.startsWith(`${m.from}/`));
|
|
54
|
+
return landed === undefined ? abs(f.path) : abs(`${landed.to}${f.path.slice(landed.from.length)}`);
|
|
55
|
+
};
|
|
56
|
+
const done = [];
|
|
57
|
+
const pending = [];
|
|
58
|
+
const unknown = [];
|
|
59
|
+
for (const f of intent.files) {
|
|
60
|
+
const file = target(f);
|
|
61
|
+
const now = existsSync(file) ? sha256(await readFile(file)) : null;
|
|
62
|
+
if (now === f.after)
|
|
63
|
+
done.push(f);
|
|
64
|
+
else if (now === f.before)
|
|
65
|
+
pending.push(f);
|
|
66
|
+
else
|
|
67
|
+
unknown.push(f.path);
|
|
68
|
+
}
|
|
69
|
+
if (unknown.length > 0 || unknownMoves.length > 0) {
|
|
70
|
+
// Two vocabularies on purpose: a FILE in neither recorded state holds
|
|
71
|
+
// edited bytes, while a DIRECTORY rename in neither state means both ends
|
|
72
|
+
// exist (or neither does) — nothing was "edited", somebody made or
|
|
73
|
+
// restored a directory since the crash. One sentence covering both told
|
|
74
|
+
// the operator to hunt for edited bytes in a directory pair. And the
|
|
75
|
+
// interrupted work is named by what it WAS (`command` + `target`), not by
|
|
76
|
+
// `intent.rerun` — that string is the REPAIR spelling (`loam subsystem
|
|
77
|
+
// sync` repairs all four subsystem verbs), so "a `loam subsystem sync`
|
|
78
|
+
// was interrupted" named a command nobody ran.
|
|
79
|
+
const parts = [
|
|
80
|
+
...(unknown.length > 0
|
|
81
|
+
? [
|
|
82
|
+
`${unknown.length} of the file(s) it was writing now hold bytes that are neither what it found ` +
|
|
83
|
+
`nor what it wrote — somebody edited them since: ${unknown.join(", ")}`,
|
|
84
|
+
]
|
|
85
|
+
: []),
|
|
86
|
+
...(unknownMoves.length > 0
|
|
87
|
+
? [
|
|
88
|
+
`the directory rename(s) ${unknownMoves.join(", ")} are in neither recorded state — both ends ` +
|
|
89
|
+
`exist, or neither does, so loam cannot tell which tree to keep`,
|
|
90
|
+
]
|
|
91
|
+
: []),
|
|
92
|
+
];
|
|
93
|
+
throw new InterruptedCommitError(`a \`${intent.command}\` commit for '${intent.target}' was interrupted mid-commit ` +
|
|
94
|
+
`(pid ${intent.pid} on ${intent.host}, ${intent.at}) and ${parts.join("; ")}. ` +
|
|
95
|
+
`loam will not choose between those two truths. ` +
|
|
96
|
+
`Reconcile them against version control, delete ${COMMIT_INTENT}, then re-run \`${intent.rerun}\`.`);
|
|
97
|
+
}
|
|
98
|
+
// Whether forward is still POSSIBLE is decided before a single byte moves:
|
|
99
|
+
// completing some files and then refusing on a missing temp would leave the
|
|
100
|
+
// repo in a state neither the old nor the new tree ever had.
|
|
101
|
+
const missing = pending.filter((f) => f.after !== null && !usableTemp(root, f));
|
|
102
|
+
if (missing.length > 0) {
|
|
103
|
+
// A file whose before and after digests are EQUAL — an unchanged file
|
|
104
|
+
// carried through a commit — reads as "done" in whichever state it is in,
|
|
105
|
+
// so it is not evidence that any rename landed. Only a file that could
|
|
106
|
+
// tell the two states apart counts as one — and a landed directory MOVE
|
|
107
|
+
// always can, so it counts too.
|
|
108
|
+
const evidence = done.filter((f) => f.before !== f.after);
|
|
109
|
+
if (evidence.length === 0 && movesDone.length === 0) {
|
|
110
|
+
// Nothing ever swapped and the temps are gone — this is the tree a
|
|
111
|
+
// completed ROLLBACK leaves when the crash landed between the rollback
|
|
112
|
+
// and the journal's removal. The complete pre-state is on disk, which
|
|
113
|
+
// is one of the two states recovery promises; declaring it beats
|
|
114
|
+
// refusing over bytes that are already consistent.
|
|
115
|
+
await finish(root, intent);
|
|
116
|
+
return { command: intent.command, feature: intent.target, outcome: "consistent", repaired: [] };
|
|
117
|
+
}
|
|
118
|
+
throw new InterruptedCommitError(`a \`${intent.command}\` commit for '${intent.target}' was interrupted and ${missing.map((f) => f.path).join(", ")} ` +
|
|
119
|
+
`${missing.length === 1 ? "is" : "are"} still in the pre-commit state, but the staged bytes that would ` +
|
|
120
|
+
`finish ${missing.length === 1 ? "it" : "them"} are gone or no longer match the digest recorded before the ` +
|
|
121
|
+
`crash. Restore the tree from version control, delete ${COMMIT_INTENT}, then re-run \`${intent.rerun}\`.`);
|
|
122
|
+
}
|
|
123
|
+
const repaired = [];
|
|
124
|
+
for (const f of pending) {
|
|
125
|
+
const file = target(f);
|
|
126
|
+
if (f.after === null) {
|
|
127
|
+
await rm(file, { force: true });
|
|
128
|
+
await quietRmdir(dirname(file));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
await rename(join(root, ...f.tmp.split("/")), file);
|
|
132
|
+
}
|
|
133
|
+
repaired.push(f.path);
|
|
134
|
+
}
|
|
135
|
+
// Renames after the file swaps, in recorded order — the order the commit
|
|
136
|
+
// itself performs them. One that cannot land now (the destination's parent
|
|
137
|
+
// deleted since the crash, a Windows EPERM on an open handle) is a refusal
|
|
138
|
+
// naming the pair, not a half-finished loop: nothing before it is undone,
|
|
139
|
+
// and the journal stays for the next attempt.
|
|
140
|
+
for (const m of movesPending) {
|
|
141
|
+
try {
|
|
142
|
+
await rename(abs(m.from), abs(m.to));
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
throw new InterruptedCommitError(`a \`${intent.command}\` commit for '${intent.target}' was interrupted and the rename ${m.from} -> ${m.to} ` +
|
|
146
|
+
`that would finish it cannot be performed now (${err instanceof Error ? err.message : String(err)}). ` +
|
|
147
|
+
`Complete or undo it by hand against version control, delete ${COMMIT_INTENT}, then re-run \`${intent.rerun}\`.`);
|
|
148
|
+
}
|
|
149
|
+
repaired.push(m.to);
|
|
150
|
+
}
|
|
151
|
+
await finish(root, intent);
|
|
152
|
+
return {
|
|
153
|
+
command: intent.command,
|
|
154
|
+
feature: intent.target,
|
|
155
|
+
outcome: repaired.length > 0 ? "repaired" : "completed",
|
|
156
|
+
repaired,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Is the recorded temp still there with exactly the bytes the journal promised? */
|
|
160
|
+
function usableTemp(root, f) {
|
|
161
|
+
if (f.tmp === null)
|
|
162
|
+
return false;
|
|
163
|
+
const path = join(root, ...f.tmp.split("/"));
|
|
164
|
+
try {
|
|
165
|
+
return sha256(readFileSync(path)) === f.after;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function finish(root, intent) {
|
|
172
|
+
const dirs = intent.files.flatMap((f) => {
|
|
173
|
+
const out = [dirname(join(root, ...f.path.split("/")))];
|
|
174
|
+
if (f.tmp !== null)
|
|
175
|
+
out.push(dirname(join(root, ...f.tmp.split("/"))));
|
|
176
|
+
return out;
|
|
177
|
+
});
|
|
178
|
+
await sweepPidTemps(dirs, intent.pid);
|
|
179
|
+
await clearTxnIntent(root);
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=forward.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../../src/core/staging/txn/forward.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Roll a version-2 or version-3 journal FORWARD: finish the renames a killed\n * commit did not reach, from the staged temps that are already durable on\n * disk (and, for a version-3 record, the directory renames after them).\n *\n * Forward and not undo, because that is what is actually durable: the\n * after-bytes were written and (per stageWrites) fully on disk before the\n * journal existed, while the OVERWRITTEN files' pre-images are durable\n * nowhere — the rename that replaced them destroyed the only copy. The plan\n * was validated completely before the first rename, so the post-state is\n * exactly what a successful run would have produced; unarchive's\n * finish-never-undo recovery is the precedent.\n *\n * The refuse-to-choose doctrine is rollbackStaged's, verbatim: a file whose\n * bytes are neither the before- nor the after-digest was edited by somebody\n * since the crash, and completing over it would destroy one of two truths.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readFile, rename, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { quietRmdir } from \"../commit.js\";\nimport { sha256 } from \"../writes.js\";\nimport { COMMIT_INTENT, type CommitRecovery, InterruptedCommitError, sweepPidTemps } from \"../interrupted.js\";\nimport { clearTxnIntent, type TxnIntent, type TxnIntentFile, type TxnIntentMove } from \"./journal.js\";\n\nexport async function recoverForward(root: string, intent: TxnIntent): Promise<CommitRecovery> {\n const abs = (rel: string): string => join(root, ...rel.split(\"/\"));\n // A directory rename is classified by pure existence — a rename moves the\n // inode, so \"which end exists\" is exactly as discriminating as a digest is\n // for a file. Both ends existing (or neither) is a state no step of the\n // commit produces: somebody made a directory since the crash, and choosing\n // which tree to keep is the refuse-to-choose case below.\n const movesDone: TxnIntentMove[] = [];\n const movesPending: TxnIntentMove[] = [];\n const unknownMoves: string[] = [];\n for (const m of intent.moves) {\n const fromExists = existsSync(abs(m.from));\n const toExists = existsSync(abs(m.to));\n if (toExists && !fromExists) movesDone.push(m);\n else if (fromExists && !toExists) movesPending.push(m);\n else unknownMoves.push(`${m.from} -> ${m.to}`);\n }\n // Where a recorded file IS now. The journal records a file at the path its\n // swap wrote — and a `subsystem move` swaps the documents whose links it\n // rewrites INSIDE a directory it then renames, so once that rename has\n // landed the file is under the moved directory, not at the recorded path.\n // Looked for there, it would read as absent: \"edited since\", a refusal over\n // a commit that only needed its journal cleared. Classified moves first for\n // exactly this; a file under a move still pending is where it was recorded.\n const target = (f: TxnIntentFile): string => {\n const landed = movesDone.find((m) => f.path.startsWith(`${m.from}/`));\n return landed === undefined ? abs(f.path) : abs(`${landed.to}${f.path.slice(landed.from.length)}`);\n };\n const done: TxnIntentFile[] = [];\n const pending: TxnIntentFile[] = [];\n const unknown: string[] = [];\n for (const f of intent.files) {\n const file = target(f);\n const now = existsSync(file) ? sha256(await readFile(file)) : null;\n if (now === f.after) done.push(f);\n else if (now === f.before) pending.push(f);\n else unknown.push(f.path);\n }\n if (unknown.length > 0 || unknownMoves.length > 0) {\n // Two vocabularies on purpose: a FILE in neither recorded state holds\n // edited bytes, while a DIRECTORY rename in neither state means both ends\n // exist (or neither does) — nothing was \"edited\", somebody made or\n // restored a directory since the crash. One sentence covering both told\n // the operator to hunt for edited bytes in a directory pair. And the\n // interrupted work is named by what it WAS (`command` + `target`), not by\n // `intent.rerun` — that string is the REPAIR spelling (`loam subsystem\n // sync` repairs all four subsystem verbs), so \"a `loam subsystem sync`\n // was interrupted\" named a command nobody ran.\n const parts = [\n ...(unknown.length > 0\n ? [\n `${unknown.length} of the file(s) it was writing now hold bytes that are neither what it found ` +\n `nor what it wrote — somebody edited them since: ${unknown.join(\", \")}`,\n ]\n : []),\n ...(unknownMoves.length > 0\n ? [\n `the directory rename(s) ${unknownMoves.join(\", \")} are in neither recorded state — both ends ` +\n `exist, or neither does, so loam cannot tell which tree to keep`,\n ]\n : []),\n ];\n throw new InterruptedCommitError(\n `a \\`${intent.command}\\` commit for '${intent.target}' was interrupted mid-commit ` +\n `(pid ${intent.pid} on ${intent.host}, ${intent.at}) and ${parts.join(\"; \")}. ` +\n `loam will not choose between those two truths. ` +\n `Reconcile them against version control, delete ${COMMIT_INTENT}, then re-run \\`${intent.rerun}\\`.`,\n );\n }\n\n // Whether forward is still POSSIBLE is decided before a single byte moves:\n // completing some files and then refusing on a missing temp would leave the\n // repo in a state neither the old nor the new tree ever had.\n const missing = pending.filter((f) => f.after !== null && !usableTemp(root, f));\n if (missing.length > 0) {\n // A file whose before and after digests are EQUAL — an unchanged file\n // carried through a commit — reads as \"done\" in whichever state it is in,\n // so it is not evidence that any rename landed. Only a file that could\n // tell the two states apart counts as one — and a landed directory MOVE\n // always can, so it counts too.\n const evidence = done.filter((f) => f.before !== f.after);\n if (evidence.length === 0 && movesDone.length === 0) {\n // Nothing ever swapped and the temps are gone — this is the tree a\n // completed ROLLBACK leaves when the crash landed between the rollback\n // and the journal's removal. The complete pre-state is on disk, which\n // is one of the two states recovery promises; declaring it beats\n // refusing over bytes that are already consistent.\n await finish(root, intent);\n return { command: intent.command, feature: intent.target, outcome: \"consistent\", repaired: [] };\n }\n throw new InterruptedCommitError(\n `a \\`${intent.command}\\` commit for '${intent.target}' was interrupted and ${missing.map((f) => f.path).join(\", \")} ` +\n `${missing.length === 1 ? \"is\" : \"are\"} still in the pre-commit state, but the staged bytes that would ` +\n `finish ${missing.length === 1 ? \"it\" : \"them\"} are gone or no longer match the digest recorded before the ` +\n `crash. Restore the tree from version control, delete ${COMMIT_INTENT}, then re-run \\`${intent.rerun}\\`.`,\n );\n }\n\n const repaired: string[] = [];\n for (const f of pending) {\n const file = target(f);\n if (f.after === null) {\n await rm(file, { force: true });\n await quietRmdir(dirname(file));\n } else {\n await rename(join(root, ...f.tmp!.split(\"/\")), file);\n }\n repaired.push(f.path);\n }\n // Renames after the file swaps, in recorded order — the order the commit\n // itself performs them. One that cannot land now (the destination's parent\n // deleted since the crash, a Windows EPERM on an open handle) is a refusal\n // naming the pair, not a half-finished loop: nothing before it is undone,\n // and the journal stays for the next attempt.\n for (const m of movesPending) {\n try {\n await rename(abs(m.from), abs(m.to));\n } catch (err) {\n throw new InterruptedCommitError(\n `a \\`${intent.command}\\` commit for '${intent.target}' was interrupted and the rename ${m.from} -> ${m.to} ` +\n `that would finish it cannot be performed now (${err instanceof Error ? err.message : String(err)}). ` +\n `Complete or undo it by hand against version control, delete ${COMMIT_INTENT}, then re-run \\`${intent.rerun}\\`.`,\n );\n }\n repaired.push(m.to);\n }\n await finish(root, intent);\n return {\n command: intent.command,\n feature: intent.target,\n outcome: repaired.length > 0 ? \"repaired\" : \"completed\",\n repaired,\n };\n}\n\n/** Is the recorded temp still there with exactly the bytes the journal promised? */\nfunction usableTemp(root: string, f: TxnIntentFile): boolean {\n if (f.tmp === null) return false;\n const path = join(root, ...f.tmp.split(\"/\"));\n try {\n return sha256(readFileSync(path)) === f.after;\n } catch {\n return false;\n }\n}\n\nasync function finish(root: string, intent: TxnIntent): Promise<void> {\n const dirs = intent.files.flatMap((f) => {\n const out = [dirname(join(root, ...f.path.split(\"/\")))];\n if (f.tmp !== null) out.push(dirname(join(root, ...f.tmp.split(\"/\"))));\n return out;\n });\n await sweepPidTemps(dirs, intent.pid);\n await clearTxnIntent(root);\n}\n\n"]}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version-2 `.loam-commit` journal: the record a SMALL journaled
|
|
3
|
+
* transaction writes before its first rename, for the writers whose commits
|
|
4
|
+
* have no snapshot and no final directory move — rebase, vouch, new, gherkin.
|
|
5
|
+
*
|
|
6
|
+
* Version 1 (archive/unarchive) restores from a snapshot that travels in the
|
|
7
|
+
* feature directory; none of the smaller writers has one, which is exactly
|
|
8
|
+
* why the roadmap says extract a smaller transaction rather than reuse — this
|
|
9
|
+
* journal therefore records, per file, the digest of both states AND the
|
|
10
|
+
* root-relative staged temp still holding the after-bytes, so recovery can
|
|
11
|
+
* roll FORWARD from what is already durable instead of pretending pre-images
|
|
12
|
+
* exist somewhere. `rerun` is the exact `loam …` line doctor prints as the
|
|
13
|
+
* fix: recovery runs at the start of the next invocation of the same command.
|
|
14
|
+
*
|
|
15
|
+
* A txn/ sub-package importing ONLY the staging root, never `recovery/`:
|
|
16
|
+
* recovery/ already imports the root and will import txn/ for dispatch, so an
|
|
17
|
+
* edge back from here would be the repo's first package cycle.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync } from "node:fs";
|
|
20
|
+
import { readFile } from "node:fs/promises";
|
|
21
|
+
import { hostname } from "node:os";
|
|
22
|
+
import { join, relative } from "node:path";
|
|
23
|
+
import { isPathInside } from "../../kernel/path-safety.js";
|
|
24
|
+
import { quietRm } from "../commit.js";
|
|
25
|
+
import { COMMIT_INTENT, writeDurable } from "../interrupted.js";
|
|
26
|
+
import { isDigest, sha256 } from "../writes.js";
|
|
27
|
+
const TXN_VERSION = 2;
|
|
28
|
+
/**
|
|
29
|
+
* The version whose records CARRY DIRECTORY MOVES — `subsystem move`'s
|
|
30
|
+
* journal, the first consumer whose commit renames directories beside its one
|
|
31
|
+
* staged file. A version of its own, and version 2 is still what every
|
|
32
|
+
* move-less writer stamps, deliberately: an old binary must keep recovering
|
|
33
|
+
* the version-2 records it always could, and it must REFUSE a record with
|
|
34
|
+
* moves rather than roll the file half forward and leave the renames behind —
|
|
35
|
+
* an unknown version already reads as "a commit this loam cannot read", which
|
|
36
|
+
* is `doctor`'s honest blocker, not a silent half-repair.
|
|
37
|
+
*/
|
|
38
|
+
const TXN_MOVES_VERSION = 3;
|
|
39
|
+
/** Root-relative with forward slashes — the journal must survive being read on another machine. */
|
|
40
|
+
function rel(root, abs) {
|
|
41
|
+
return relative(root, abs).split(/[\\/]/).join("/");
|
|
42
|
+
}
|
|
43
|
+
export async function writeTxnIntent(spec, staged) {
|
|
44
|
+
const moves = (spec.moves ?? []).map((m) => ({ from: rel(spec.root, m.from), to: rel(spec.root, m.to) }));
|
|
45
|
+
const intent = {
|
|
46
|
+
version: moves.length > 0 ? TXN_MOVES_VERSION : TXN_VERSION,
|
|
47
|
+
command: spec.command,
|
|
48
|
+
rerun: spec.rerun,
|
|
49
|
+
target: spec.target,
|
|
50
|
+
pid: process.pid,
|
|
51
|
+
host: hostname(),
|
|
52
|
+
at: new Date().toISOString(),
|
|
53
|
+
files: staged.map((s) => ({
|
|
54
|
+
path: rel(spec.root, s.write.path),
|
|
55
|
+
before: s.before === null ? null : sha256(s.before),
|
|
56
|
+
after: s.content === null ? null : sha256(s.content),
|
|
57
|
+
tmp: s.tmp === null ? null : rel(spec.root, s.tmp),
|
|
58
|
+
})),
|
|
59
|
+
moves,
|
|
60
|
+
};
|
|
61
|
+
await writeDurable(join(spec.root, COMMIT_INTENT), JSON.stringify(intent, null, 2) + "\n");
|
|
62
|
+
}
|
|
63
|
+
export async function clearTxnIntent(root) {
|
|
64
|
+
await quietRm(join(root, COMMIT_INTENT));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The journal as written, or null when absent, unreadable, or not version 2 —
|
|
68
|
+
* the same strict grading version 1 gets: a record that cannot be trusted
|
|
69
|
+
* whole is not a record, and `doctor` describes the difference.
|
|
70
|
+
*/
|
|
71
|
+
export async function readTxnIntent(root) {
|
|
72
|
+
const path = join(root, COMMIT_INTENT);
|
|
73
|
+
if (!existsSync(path))
|
|
74
|
+
return null;
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const i = parsed;
|
|
83
|
+
if (i === null || typeof i !== "object")
|
|
84
|
+
return null;
|
|
85
|
+
if (i.version !== TXN_VERSION && i.version !== TXN_MOVES_VERSION)
|
|
86
|
+
return null;
|
|
87
|
+
if (typeof i.command !== "string" || typeof i.rerun !== "string" || typeof i.target !== "string")
|
|
88
|
+
return null;
|
|
89
|
+
if (typeof i.pid !== "number" || typeof i.host !== "string" || typeof i.at !== "string")
|
|
90
|
+
return null;
|
|
91
|
+
if (!Array.isArray(i.files))
|
|
92
|
+
return null;
|
|
93
|
+
// A version-2 record predates the field; normalise so recovery reads one
|
|
94
|
+
// shape. A version-3 record's moves are validated exactly as its files are:
|
|
95
|
+
// recovery RENAMES what they name, so an entry escaping the root fails the
|
|
96
|
+
// whole record closed.
|
|
97
|
+
if (i.moves === undefined)
|
|
98
|
+
i.moves = [];
|
|
99
|
+
if (!Array.isArray(i.moves))
|
|
100
|
+
return null;
|
|
101
|
+
if (i.version === TXN_VERSION && i.moves.length > 0)
|
|
102
|
+
return null;
|
|
103
|
+
for (const m of i.moves) {
|
|
104
|
+
if (typeof m?.from !== "string" || typeof m?.to !== "string")
|
|
105
|
+
return null;
|
|
106
|
+
if (!insideRoot(root, m.from) || !insideRoot(root, m.to))
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
for (const f of i.files) {
|
|
110
|
+
if (typeof f?.path !== "string")
|
|
111
|
+
return null;
|
|
112
|
+
if (!digestOrNull(f.before) || !digestOrNull(f.after))
|
|
113
|
+
return null;
|
|
114
|
+
if (f.tmp !== null && typeof f.tmp !== "string")
|
|
115
|
+
return null;
|
|
116
|
+
// A write (after non-null) with no temp recorded could never be rolled
|
|
117
|
+
// forward; refusing the whole record here keeps that impossibility a
|
|
118
|
+
// `doctor` sentence instead of a mid-recovery surprise.
|
|
119
|
+
if (f.after !== null && f.tmp === null)
|
|
120
|
+
return null;
|
|
121
|
+
// The journal is untrusted input — a plain file that travels in a git
|
|
122
|
+
// checkout — and recovery RENAMES OVER and DELETES what these entries
|
|
123
|
+
// name. An entry escaping the root (`../outside`, an absolute path) would
|
|
124
|
+
// turn roll-forward into an arbitrary write anywhere on the machine, so
|
|
125
|
+
// it fails the whole record closed: `doctor` grades it exactly as any
|
|
126
|
+
// other unreadable journal, a blocker naming the file for a human.
|
|
127
|
+
if (!insideRoot(root, f.path) || (f.tmp !== null && !insideRoot(root, f.tmp)))
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return i;
|
|
131
|
+
}
|
|
132
|
+
function digestOrNull(value) {
|
|
133
|
+
return value === null || isDigest(value);
|
|
134
|
+
}
|
|
135
|
+
/** Lexical containment of a recorded root-relative path — recovery acts on the join. */
|
|
136
|
+
function insideRoot(root, rel) {
|
|
137
|
+
return isPathInside(join(root), join(root, ...rel.split("/")));
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../../../src/core/staging/txn/journal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The version-2 `.loam-commit` journal: the record a SMALL journaled\n * transaction writes before its first rename, for the writers whose commits\n * have no snapshot and no final directory move — rebase, vouch, new, gherkin.\n *\n * Version 1 (archive/unarchive) restores from a snapshot that travels in the\n * feature directory; none of the smaller writers has one, which is exactly\n * why the roadmap says extract a smaller transaction rather than reuse — this\n * journal therefore records, per file, the digest of both states AND the\n * root-relative staged temp still holding the after-bytes, so recovery can\n * roll FORWARD from what is already durable instead of pretending pre-images\n * exist somewhere. `rerun` is the exact `loam …` line doctor prints as the\n * fix: recovery runs at the start of the next invocation of the same command.\n *\n * A txn/ sub-package importing ONLY the staging root, never `recovery/`:\n * recovery/ already imports the root and will import txn/ for dispatch, so an\n * edge back from here would be the repo's first package cycle.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { hostname } from \"node:os\";\nimport { join, relative } from \"node:path\";\nimport { isPathInside } from \"../../kernel/path-safety.js\";\nimport { quietRm } from \"../commit.js\";\nimport { COMMIT_INTENT, writeDurable } from \"../interrupted.js\";\nimport { isDigest, sha256, type StagedWrite } from \"../writes.js\";\n\nconst TXN_VERSION = 2;\n/**\n * The version whose records CARRY DIRECTORY MOVES — `subsystem move`'s\n * journal, the first consumer whose commit renames directories beside its one\n * staged file. A version of its own, and version 2 is still what every\n * move-less writer stamps, deliberately: an old binary must keep recovering\n * the version-2 records it always could, and it must REFUSE a record with\n * moves rather than roll the file half forward and leave the renames behind —\n * an unknown version already reads as \"a commit this loam cannot read\", which\n * is `doctor`'s honest blocker, not a silent half-repair.\n */\nconst TXN_MOVES_VERSION = 3;\n\n/** One directory rename inside a transaction: root-relative, forward slashes. */\nexport interface TxnIntentMove {\n from: string;\n to: string;\n}\n\nexport interface TxnIntentFile {\n /** Root-relative path, forward slashes. */\n path: string;\n /** sha256 of the bytes before the swap; null when the swap creates the file. */\n before: string | null;\n /** sha256 of the bytes the swap writes; null when the swap deletes the file. */\n after: string | null;\n /** Root-relative staged temp holding the after-bytes; null for a deletion. */\n tmp: string | null;\n}\n\nexport interface TxnIntent {\n version: number;\n /** Which command was committing — a sentence a human reads, not a branch. */\n command: string;\n /** The exact `loam …` invocation whose next run recovers this journal. */\n rerun: string;\n /** What the command was committing for: a feature id, or a service. */\n target: string;\n pid: number;\n host: string;\n at: string;\n files: TxnIntentFile[];\n /**\n * Directory renames the commit performs AFTER its file swaps, in this\n * order. `[]` on every version-2 record (the reader normalises absence):\n * only `subsystem move`/`rename` write any, and their presence is what\n * bumps the record to version 3.\n */\n moves: TxnIntentMove[];\n}\n\n/** What a transaction tells the journal about itself; the caller's one spelling of its identity. */\nexport interface TxnSpec {\n /** The repo root the journal lives in — the docs repo, or gherkin's owned root. */\n root: string;\n command: string;\n rerun: string;\n target: string;\n /** Directory renames to perform after the swaps — ABSOLUTE paths; the journal stores them root-relative. */\n moves?: { from: string; to: string }[];\n}\n\n/** Root-relative with forward slashes — the journal must survive being read on another machine. */\nfunction rel(root: string, abs: string): string {\n return relative(root, abs).split(/[\\\\/]/).join(\"/\");\n}\n\nexport async function writeTxnIntent(spec: TxnSpec, staged: StagedWrite[]): Promise<void> {\n const moves = (spec.moves ?? []).map((m) => ({ from: rel(spec.root, m.from), to: rel(spec.root, m.to) }));\n const intent: TxnIntent = {\n version: moves.length > 0 ? TXN_MOVES_VERSION : TXN_VERSION,\n command: spec.command,\n rerun: spec.rerun,\n target: spec.target,\n pid: process.pid,\n host: hostname(),\n at: new Date().toISOString(),\n files: staged.map((s) => ({\n path: rel(spec.root, s.write.path),\n before: s.before === null ? null : sha256(s.before),\n after: s.content === null ? null : sha256(s.content),\n tmp: s.tmp === null ? null : rel(spec.root, s.tmp),\n })),\n moves,\n };\n await writeDurable(join(spec.root, COMMIT_INTENT), JSON.stringify(intent, null, 2) + \"\\n\");\n}\n\nexport async function clearTxnIntent(root: string): Promise<void> {\n await quietRm(join(root, COMMIT_INTENT));\n}\n\n/**\n * The journal as written, or null when absent, unreadable, or not version 2 —\n * the same strict grading version 1 gets: a record that cannot be trusted\n * whole is not a record, and `doctor` describes the difference.\n */\nexport async function readTxnIntent(root: string): Promise<TxnIntent | null> {\n const path = join(root, COMMIT_INTENT);\n if (!existsSync(path)) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(path, \"utf8\"));\n } catch {\n return null;\n }\n const i = parsed as Partial<TxnIntent>;\n if (i === null || typeof i !== \"object\") return null;\n if (i.version !== TXN_VERSION && i.version !== TXN_MOVES_VERSION) return null;\n if (typeof i.command !== \"string\" || typeof i.rerun !== \"string\" || typeof i.target !== \"string\") return null;\n if (typeof i.pid !== \"number\" || typeof i.host !== \"string\" || typeof i.at !== \"string\") return null;\n if (!Array.isArray(i.files)) return null;\n // A version-2 record predates the field; normalise so recovery reads one\n // shape. A version-3 record's moves are validated exactly as its files are:\n // recovery RENAMES what they name, so an entry escaping the root fails the\n // whole record closed.\n if (i.moves === undefined) i.moves = [];\n if (!Array.isArray(i.moves)) return null;\n if (i.version === TXN_VERSION && i.moves.length > 0) return null;\n for (const m of i.moves) {\n if (typeof m?.from !== \"string\" || typeof m?.to !== \"string\") return null;\n if (!insideRoot(root, m.from) || !insideRoot(root, m.to)) return null;\n }\n for (const f of i.files) {\n if (typeof f?.path !== \"string\") return null;\n if (!digestOrNull(f.before) || !digestOrNull(f.after)) return null;\n if (f.tmp !== null && typeof f.tmp !== \"string\") return null;\n // A write (after non-null) with no temp recorded could never be rolled\n // forward; refusing the whole record here keeps that impossibility a\n // `doctor` sentence instead of a mid-recovery surprise.\n if (f.after !== null && f.tmp === null) return null;\n // The journal is untrusted input — a plain file that travels in a git\n // checkout — and recovery RENAMES OVER and DELETES what these entries\n // name. An entry escaping the root (`../outside`, an absolute path) would\n // turn roll-forward into an arbitrary write anywhere on the machine, so\n // it fails the whole record closed: `doctor` grades it exactly as any\n // other unreadable journal, a blocker naming the file for a human.\n if (!insideRoot(root, f.path) || (f.tmp !== null && !insideRoot(root, f.tmp))) return null;\n }\n return i as TxnIntent;\n}\n\nfunction digestOrNull(value: unknown): boolean {\n return value === null || isDigest(value);\n}\n\n/** Lexical containment of a recorded root-relative path — recovery acts on the join. */\nfunction insideRoot(root: string, rel: string): boolean {\n return isPathInside(join(root), join(root, ...rel.split(\"/\")));\n}\n"]}
|