@ybotok/loam 0.2.0-alpha.6 → 0.2.0-alpha.9
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 +1477 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +108 -11
- package/SCHEMA.md +721 -130
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- 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 +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +82 -33
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- 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 +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- 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 +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +64 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +60 -25
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +141 -71
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/report.js +55 -5
- package/dist/commands/subsystem/txn/report.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +121 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +82 -12
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +26 -3
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +35 -32
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +7 -12
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +16 -5
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/consumers.js +43 -122
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/ids.js +33 -7
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +17 -35
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/fleet/views/stale.js +31 -14
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- 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 +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +57 -102
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +31 -17
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +37 -21
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +25 -10
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +25 -22
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +62 -24
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +30 -25
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +79 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +1 -1
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/architecture.js +31 -9
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/documents.js +36 -10
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +14 -21
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +7 -3
- package/dist/core/c4/project/staged.js.map +1 -1
- 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 +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- 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/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- 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/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/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +15 -16
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- 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 +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- 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 +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +10 -3
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +64 -19
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- 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 +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +34 -29
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +45 -6
- package/dist/core/repo/tree/render/body.js.map +1 -1
- 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 +19 -2
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +102 -33
- package/dist/core/repo/tree/render/views.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -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 +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- 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/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +45 -10
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- 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 +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +53 -3
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +28 -9
- package/examples/docs/architecture/landscape.likec4 +21 -7
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +3 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +10 -8
- package/examples/docs/services/payment-service/model.likec4 +6 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/commands/tasks/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The terminal view of a work packet.\n *\n * Two shapes for two readers. The whole packet is a person's orientation — how\n * much is open, filed under which service, and which ten units lead — so it is\n * capped per service and never prints a claim twice. `--next` is the opposite: a\n * single leaf, printed in full, including the scenario's Given/When/Then\n * VERBATIM, because that text is the acceptance criterion and the exact words\n * the digest was taken over. Paraphrasing it into a summary is the one thing the\n * digest chain exists to prevent.\n *\n * Every `loam …` line printed here parses against the real CLI —\n * `test/agent-commands-runnable.test.ts` is the consumer, and an instruction\n * loam prints that does not parse is a defect rather than a typo.\n */\nimport type { WorkPacket } from \"../../core/tasks/packet.js\";\nimport type { TaskUnit } from \"../../core/tasks/unit.js\";\n\n/** How many units one service shows before the rest are elided behind its own lens. */\nconst PER_SERVICE_LIMIT = 12;\n\n/** `path:line`, or just the path — a source with no line is still worth opening. */\nfunction at(unit: TaskUnit): string {\n const source = unit.source;\n if (source === undefined) return \"\";\n return source.line === undefined ? ` (${source.path})` : ` (${source.path}:${source.line})`;\n}\n\nfunction line(unit: TaskUnit): string {\n return ` [${unit.at}] ${unit.kind} ${unit.claim}${at(unit)}`;\n}\n\nexport function renderPacket(packet: WorkPacket): void {\n console.log(\n `${packet.feature} — ${packet.counts.open} open · ${packet.counts.done} done · ${packet.counts.attested} attested`,\n );\n console.log(` record: ${packet.record} · stamps: ${packet.stamps}${packet.service === null ? \"\" : ` · lens: ${packet.service}`}`);\n // The blind spots FIRST, and before any list: an empty packet over a document\n // nobody could read is the one output an implementer must never take at face\n // value, and a warning under fifty lines of units is a warning nobody reads.\n for (const warning of blindSpots(packet)) console.log(` ⚠ ${warning}`);\n console.log(\"\");\n\n const subjects = [...new Set(packet.units.map((unit) => unit.subject))];\n if (packet.units.length === 0) {\n console.log(\n packet.counts.attested > 0 || packet.counts.done > 0\n ? \" Nothing open. Every unit is recorded confirmed, or attested and waiting on a run.\"\n : \" Nothing open — this feature's delta, specs and contracts promise nothing yet.\",\n );\n }\n for (const subject of subjects) {\n const units = packet.units.filter((unit) => unit.subject === subject);\n console.log(` ${subject}`);\n for (const unit of units.slice(0, PER_SERVICE_LIMIT)) console.log(line(unit));\n if (units.length > PER_SERVICE_LIMIT) {\n console.log(\n ` +${units.length - PER_SERVICE_LIMIT} more — loam tasks ${packet.feature} --service ${subject} --json`,\n );\n }\n console.log(\"\");\n }\n\n if (packet.frontier.length > 0) {\n console.log(` frontier: ${packet.frontier.join(\", \")}${packet.frontierElided > 0 ? ` (+${packet.frontierElided})` : \"\"}`);\n }\n for (const waiting of packet.waitingOn ?? []) {\n console.log(` waiting on ${waiting.subject}: ${waiting.id} ${waiting.claim}`);\n }\n console.log(\"\\n Review:\");\n for (const note of packet.review) console.log(` - ${note}`);\n}\n\n/**\n * The leaf, in full. The follow-up command is literal rather than a shape to\n * fill in, because the reader is usually a model cheap enough that \"the\n * appropriate verify invocation\" is a guess it will get wrong.\n */\nexport function renderNext(packet: WorkPacket): void {\n const unit = packet.units[0];\n if (unit === undefined) {\n console.log(`${packet.feature} — nothing open (${packet.counts.done} done · ${packet.counts.attested} attested)`);\n console.log(` record: ${packet.record} · stamps: ${packet.stamps}`);\n for (const warning of blindSpots(packet)) console.log(` ⚠ ${warning}`);\n return;\n }\n console.log(`${packet.feature} — next: ${unit.id}`);\n for (const warning of blindSpots(packet)) console.log(` ⚠ ${warning}`);\n console.log(\"\");\n console.log(` [${unit.at}] ${unit.kind} [${unit.subject}]`);\n console.log(` ${unit.claim}`);\n const source = unit.source;\n if (source !== undefined) {\n console.log(` ${source.path}${source.line === undefined ? \"\" : `:${source.line}`}`);\n // Verbatim, never reflowed: these are the bytes the scenario digest was\n // taken over, and a test written from reworded criteria answers a claim\n // nobody made.\n if (\"lines\" in source) for (const body of source.lines) console.log(` ${body}`);\n }\n if (unit.dependsOn.length > 0) console.log(` depends on: ${unit.dependsOn.join(\", \")}`);\n console.log(`\\n done: ${unit.done.check}`);\n console.log(`\\n ${followUp(packet.feature, unit)}`);\n}\n\n/**\n * The command that closes this unit.\n *\n * A `describe` unit is the one arm that is NOT a `loam verify --record`: there\n * is no claim to answer, because a requirement with no scenario asks no\n * question — the work is writing the Given/When/Then into the file named above,\n * and `loam validate --feature` is what grades that it landed. Pointing it at\n * `--record` would invite an answer to a question the checklist does not carry.\n */\nfunction followUp(feature: string, unit: TaskUnit): string {\n if (unit.at === \"describe\") return `loam validate --feature ${feature}`;\n if (unit.at === \"generate\") return `loam gherkin ${feature}`;\n if (unit.kind === \"scenario.tested\") {\n return `loam verify ${feature} --service ${unit.subject} --results report.json`;\n }\n return `loam verify ${feature} --record answers.json --service ${unit.subject}`;\n}\n\n/** What the packet could not look at — one sentence per document class. */\nfunction blindSpots(packet: WorkPacket): string[] {\n const out: string[] = [];\n if (packet.unreadable.architecture !== undefined) {\n out.push(\n `delta.likec4 does not parse (${packet.unreadable.architecture}) — no c4.calls unit could be derived, ` +\n \"so this list is short because loam could not look, not because the work is done\",\n );\n }\n const contracts = packet.unreadable.contracts;\n if (contracts !== undefined) {\n out.push(\n `contract delta unreadable for ${contracts.join(\", \")} — operations and messages are missing from this packet, ` +\n \"not absent from the feature\",\n );\n }\n return out;\n}\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { configOrRefuse, emitJson, fail } from "../../core/envelope/json.js";
|
|
2
|
+
import { InvalidIdError, assertServiceId } from "../../core/kernel/ids/service.js";
|
|
3
|
+
import { livingServices } from "../../core/projection/arch-slice.js";
|
|
4
|
+
import { nearestIds } from "../../core/repo/entries.js";
|
|
5
|
+
import { missingFeatureMessage, resolveFeature } from "../../core/repo/repo.js";
|
|
6
|
+
import { workPacket } from "../../core/tasks/packet.js";
|
|
7
|
+
import { renderNext, renderPacket } from "./render.js";
|
|
8
|
+
export function registerTasks(program) {
|
|
9
|
+
program
|
|
10
|
+
.command("tasks")
|
|
11
|
+
.argument("<featureId>", "feature id, e.g. FEAT-101")
|
|
12
|
+
.description("What is still open on a feature, at which step, and what closes each unit — read-only")
|
|
13
|
+
.option("--service <id>", "narrow the packet to the units one service owns (a lens: no binding needed)")
|
|
14
|
+
.option("--next", "the frontier head alone — the single leaf to hand to a worker")
|
|
15
|
+
.option("--json", "emit the machine contract instead of the human view")
|
|
16
|
+
.action(async (featureId, opts) => {
|
|
17
|
+
const json = opts.json === true;
|
|
18
|
+
const config = await configOrRefuse(json);
|
|
19
|
+
if (config === null)
|
|
20
|
+
return;
|
|
21
|
+
// The id grammar first, and on the RAW argument, exactly as `loam delta`
|
|
22
|
+
// does: `--service ../../etc` reaches `featureSpecPaths` and reads from
|
|
23
|
+
// outside the feature directory. One grammar for the whole tool.
|
|
24
|
+
if (opts.service !== undefined) {
|
|
25
|
+
try {
|
|
26
|
+
assertServiceId(opts.service, "--service");
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (!(err instanceof InvalidIdError))
|
|
30
|
+
throw err;
|
|
31
|
+
fail(json, "invalid-option", err.message);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// "exclude", so an ARCHIVED feature gets the shared missing-feature
|
|
36
|
+
// message rather than a packet. This is refusal by honesty: archive merges
|
|
37
|
+
// a feature's operations into the living contracts, so a re-derived
|
|
38
|
+
// checklist over an archived feature mismatches BY CONSTRUCTION — every
|
|
39
|
+
// api.exposes claim it once made now reads as already-living and is never
|
|
40
|
+
// minted. A packet over that is not a short list, it is an invented one.
|
|
41
|
+
// `loam verify <id>` still reads the frozen record, and the message says so.
|
|
42
|
+
const feature = await resolveFeature(config.docsDir, featureId, "exclude");
|
|
43
|
+
if (!feature) {
|
|
44
|
+
fail(json, "unknown-target", await missingFeatureMessage(config.docsDir, featureId));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const lens = opts.service ?? null;
|
|
48
|
+
const packet = await workPacket({
|
|
49
|
+
docsDir: config.docsDir,
|
|
50
|
+
feature,
|
|
51
|
+
lens,
|
|
52
|
+
// `config.root`, never the cwd: loam.json discovery walks upward, and a
|
|
53
|
+
// run from `src/deep/` must read the same generated suite as one from
|
|
54
|
+
// the root. The same reasoning `loam gherkin` records for its writes.
|
|
55
|
+
binding: {
|
|
56
|
+
repoDir: config.root ?? process.cwd(),
|
|
57
|
+
...(config.service === undefined ? {} : { service: config.service }),
|
|
58
|
+
...(config.gherkinDir === undefined ? {} : { gherkinDir: config.gherkinDir }),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
// The lens is checked AFTER the derivation, and on the packet's own
|
|
62
|
+
// service list, because that list is the feature's claim subjects — which
|
|
63
|
+
// includes a service the C4 delta introduces and no `specs/` directory
|
|
64
|
+
// names. Checking first would need a second LikeC4 parse of the same
|
|
65
|
+
// delta to learn the same answer. A typo must never be indistinguishable
|
|
66
|
+
// from a finished service: `--service paymnt-service` produces a
|
|
67
|
+
// perfectly plausible EMPTY packet, and an agent reads that as "nothing
|
|
68
|
+
// to do here".
|
|
69
|
+
if (lens !== null && !packet.services.includes(lens)) {
|
|
70
|
+
const living = await livingServices(config.docsDir);
|
|
71
|
+
if (!living.includes(lens)) {
|
|
72
|
+
const near = nearestIds(lens, [...new Set([...packet.services, ...living])]);
|
|
73
|
+
fail(json, "unknown-target", `No service '${lens}': it is neither a services/${lens}/ in the docs repo nor a service ${feature.id} touches` +
|
|
74
|
+
(packet.services.length === 0 ? "" : ` (${packet.services.join(", ")})`) +
|
|
75
|
+
(near.length === 0 ? "." : ` — did you mean ${near.map((n) => `'${n}'`).join(" or ")}?`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// A feeder that could not be read empties the packet, and an agent
|
|
80
|
+
// consuming this as a task list reads an empty packet as "nothing to do":
|
|
81
|
+
// the vacuously-green pattern `loam delta` guards against on the same
|
|
82
|
+
// three documents. The payload stays as informative as ever and `ok`
|
|
83
|
+
// stays true under --json — the command ran — and the exit code is what
|
|
84
|
+
// stops a pipeline from building on it. Set BEFORE the format fork: the
|
|
85
|
+
// guard is about the documents, not about how they are rendered.
|
|
86
|
+
const blind = Object.keys(packet.unreadable).length > 0;
|
|
87
|
+
if (blind)
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
const shown = opts.next === true ? leaf(packet) : packet;
|
|
90
|
+
if (json) {
|
|
91
|
+
emitJson({ command: "tasks", ...shown });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (opts.next === true)
|
|
95
|
+
renderNext(shown);
|
|
96
|
+
else
|
|
97
|
+
renderPacket(shown);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The leaf packet: the same payload with `units` narrowed to the frontier head.
|
|
102
|
+
*
|
|
103
|
+
* Everything else is left whole on purpose — the counts, the frontier and the
|
|
104
|
+
* review notes are the context that stops a cheap model handed one unit from
|
|
105
|
+
* believing it is the whole job. An empty `units` when nothing is open is the
|
|
106
|
+
* honest answer and not an error: the feature's open work is done, or the
|
|
107
|
+
* record says why the packet cannot see it.
|
|
108
|
+
*/
|
|
109
|
+
function leaf(packet) {
|
|
110
|
+
const head = packet.frontier[0];
|
|
111
|
+
return { ...packet, units: packet.units.filter((unit) => unit.id === head) };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../../src/commands/tasks/tasks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `loam tasks` — the third rendering of one ledger, and a pure read.\n *\n * `loam verify` answers \"answered by whom\". `loam status` answers \"which\n * artifact is at which rung\". This answers the question an implementer actually\n * holds: what is still open, at which step, and what closes it. Nothing is\n * written, nothing is cached, and there is no state that says a unit is\n * finished — done is \"the unit no longer appears\", so the list cannot be\n * falsified by editing a checkbox. Everything in it re-derives from the tree, so\n * an orchestrator can ask again and compare rather than trusting a report.\n *\n * THE DOCS-REPO GATE IS DELIBERATELY ABSENT, following `loam delta` rather than\n * `loam verify`. This command is delta's sibling — a projection of ONE feature's\n * own documents — and its first act is `resolveFeature`, whose enumeration\n * carries the docs-repo precondition itself. Adding `docsRepoReady(json, …,\n * \"services\")` here would also refuse a features-only docs repo that `delta`\n * reads perfectly well today, which would be a new gate wearing the clothes of a\n * new command.\n */\nimport type { Command } from \"commander\";\nimport { configOrRefuse, emitJson, fail } from \"../../core/envelope/json.js\";\nimport { InvalidIdError, assertServiceId } from \"../../core/kernel/ids/service.js\";\nimport { livingServices } from \"../../core/projection/arch-slice.js\";\nimport { nearestIds } from \"../../core/repo/entries.js\";\nimport { missingFeatureMessage, resolveFeature } from \"../../core/repo/repo.js\";\nimport { workPacket, type WorkPacket } from \"../../core/tasks/packet.js\";\nimport { renderNext, renderPacket } from \"./render.js\";\n\ninterface TasksOptions {\n service?: string;\n next?: boolean;\n json?: boolean;\n}\n\nexport function registerTasks(program: Command): void {\n program\n .command(\"tasks\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"What is still open on a feature, at which step, and what closes each unit — read-only\")\n .option(\"--service <id>\", \"narrow the packet to the units one service owns (a lens: no binding needed)\")\n .option(\"--next\", \"the frontier head alone — the single leaf to hand to a worker\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureId: string, opts: TasksOptions) => {\n const json = opts.json === true;\n const config = await configOrRefuse(json);\n if (config === null) return;\n\n // The id grammar first, and on the RAW argument, exactly as `loam delta`\n // does: `--service ../../etc` reaches `featureSpecPaths` and reads from\n // outside the feature directory. One grammar for the whole tool.\n if (opts.service !== undefined) {\n try {\n assertServiceId(opts.service, \"--service\");\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n }\n\n // \"exclude\", so an ARCHIVED feature gets the shared missing-feature\n // message rather than a packet. This is refusal by honesty: archive merges\n // a feature's operations into the living contracts, so a re-derived\n // checklist over an archived feature mismatches BY CONSTRUCTION — every\n // api.exposes claim it once made now reads as already-living and is never\n // minted. A packet over that is not a short list, it is an invented one.\n // `loam verify <id>` still reads the frozen record, and the message says so.\n const feature = await resolveFeature(config.docsDir, featureId, \"exclude\");\n if (!feature) {\n fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureId));\n return;\n }\n\n const lens = opts.service ?? null;\n const packet = await workPacket({\n docsDir: config.docsDir,\n feature,\n lens,\n // `config.root`, never the cwd: loam.json discovery walks upward, and a\n // run from `src/deep/` must read the same generated suite as one from\n // the root. The same reasoning `loam gherkin` records for its writes.\n binding: {\n repoDir: config.root ?? process.cwd(),\n ...(config.service === undefined ? {} : { service: config.service }),\n ...(config.gherkinDir === undefined ? {} : { gherkinDir: config.gherkinDir }),\n },\n });\n\n // The lens is checked AFTER the derivation, and on the packet's own\n // service list, because that list is the feature's claim subjects — which\n // includes a service the C4 delta introduces and no `specs/` directory\n // names. Checking first would need a second LikeC4 parse of the same\n // delta to learn the same answer. A typo must never be indistinguishable\n // from a finished service: `--service paymnt-service` produces a\n // perfectly plausible EMPTY packet, and an agent reads that as \"nothing\n // to do here\".\n if (lens !== null && !packet.services.includes(lens)) {\n const living = await livingServices(config.docsDir);\n if (!living.includes(lens)) {\n const near = nearestIds(lens, [...new Set([...packet.services, ...living])]);\n fail(\n json,\n \"unknown-target\",\n `No service '${lens}': it is neither a services/${lens}/ in the docs repo nor a service ${feature.id} touches` +\n (packet.services.length === 0 ? \"\" : ` (${packet.services.join(\", \")})`) +\n (near.length === 0 ? \".\" : ` — did you mean ${near.map((n) => `'${n}'`).join(\" or \")}?`),\n );\n return;\n }\n }\n\n // A feeder that could not be read empties the packet, and an agent\n // consuming this as a task list reads an empty packet as \"nothing to do\":\n // the vacuously-green pattern `loam delta` guards against on the same\n // three documents. The payload stays as informative as ever and `ok`\n // stays true under --json — the command ran — and the exit code is what\n // stops a pipeline from building on it. Set BEFORE the format fork: the\n // guard is about the documents, not about how they are rendered.\n const blind = Object.keys(packet.unreadable).length > 0;\n if (blind) process.exitCode = 1;\n\n const shown = opts.next === true ? leaf(packet) : packet;\n if (json) {\n emitJson({ command: \"tasks\", ...shown });\n return;\n }\n if (opts.next === true) renderNext(shown);\n else renderPacket(shown);\n });\n}\n\n/**\n * The leaf packet: the same payload with `units` narrowed to the frontier head.\n *\n * Everything else is left whole on purpose — the counts, the frontier and the\n * review notes are the context that stops a cheap model handed one unit from\n * believing it is the whole job. An empty `units` when nothing is open is the\n * honest answer and not an error: the feature's open work is done, or the\n * record says why the packet cannot see it.\n */\nfunction leaf(packet: WorkPacket): WorkPacket {\n const head = packet.frontier[0];\n return { ...packet, units: packet.units.filter((unit) => unit.id === head) };\n}\n"]}
|
|
@@ -11,17 +11,14 @@
|
|
|
11
11
|
import { existsSync, lstatSync } from "node:fs";
|
|
12
12
|
import { readFile } from "node:fs/promises";
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
-
import {} from "../../core/envelope/json.js";
|
|
15
14
|
import { resolveInside, resolvePortableFileInside } from "../../core/kernel/path-safety.js";
|
|
16
15
|
import { isRecord } from "../../core/kernel/records.js";
|
|
17
|
-
import { SNAPSHOT_COMPAT_VERSION, SNAPSHOT_DIR, SNAPSHOT_MANIFEST, SNAPSHOT_VERSION, } from "../../core/staging/snapshot.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
}
|
|
16
|
+
import { SNAPSHOT_COMPAT_VERSION, SNAPSHOT_DIR, SNAPSHOT_MANIFEST, SNAPSHOT_VERSION, } from "../../core/staging/snapshot/manifest.js";
|
|
17
|
+
/**
|
|
18
|
+
* The snapshot manifest, or null when there is nothing this command can honour —
|
|
19
|
+
* absent, unreadable, or a layout version it would have to guess at. All three are
|
|
20
|
+
* the same answer to the caller: this feature cannot be unarchived automatically.
|
|
21
|
+
*/
|
|
25
22
|
export async function readManifest(request) {
|
|
26
23
|
const { featureDir, docsDir, featureId, dirName } = request;
|
|
27
24
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/commands/unarchive/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The snapshot manifest, validated before a single byte is written back.\n *\n * `unarchive` says \"I put your docs back\", and that sentence must never be\n * sayable over a pre-image edited in the archived feature directory since. Each\n * entry carries the digest of what archive WROTE and the digest of what will be\n * RESTORED, and this is where both are checked — version, timestamp, feature\n * name, duplicate paths and all — so the restore below it can assume the file\n * describes the archive it claims to.\n */\nimport { existsSync, lstatSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { type ErrorCode } from \"../../core/envelope/json.js\";\nimport { resolveInside, resolvePortableFileInside } from \"../../core/kernel/path-safety.js\";\nimport { isRecord } from \"../../core/kernel/records.js\";\nimport {\n SNAPSHOT_COMPAT_VERSION,\n SNAPSHOT_DIR,\n SNAPSHOT_MANIFEST,\n SNAPSHOT_VERSION,\n type SnapshotManifest,\n} from \"../../core/staging/snapshot.js\";\n\nexport class RestoreFailure extends Error {\n constructor(\n readonly code: ErrorCode,\n msg: string,\n ) {\n super(msg);\n }\n}\n\nexport interface ValidatedSnapshotEntry {\n path: string;\n existed: boolean;\n after: string;\n /** sha256 the pre-image beside the manifest must still have; null when archive created the destination. */\n before: string | null;\n /** Contained destination under the docs repo. */\n target: string;\n /** Contained pre-image, or null when archive created the destination. */\n snapshot: string | null;\n}\n\nexport interface ValidatedSnapshotManifest extends Omit<SnapshotManifest, \"files\"> {\n files: ValidatedSnapshotEntry[];\n}\n\n/**\n * `readManifest`'s inputs, as one object. `serviceDirOf` is the restore-time\n * half of the version-3 re-keying: a service id → its CURRENT docs-relative\n * directory per the enumeration, or null when no such service is enumerated.\n * Injected by the caller (which holds the enumeration) rather than resolved\n * here, so this module keeps validating one file against one repo state.\n */\nexport interface ManifestRequest {\n featureDir: string;\n docsDir: string;\n featureId: string;\n dirName: string;\n serviceDirOf: (service: string) => string | null;\n}\n\nexport async function readManifest(request: ManifestRequest): Promise<ValidatedSnapshotManifest | null> {\n const { featureDir, docsDir, featureId, dirName } = request;\n try {\n const path = resolveInside(\n featureDir,\n join(SNAPSHOT_DIR, SNAPSHOT_MANIFEST),\n \"snapshot manifest path\",\n );\n if (!existsSync(path)) return null;\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as unknown;\n if (!isRecord(parsed)) return null;\n // Version 3 is what archive writes; version 2 is read forever under its own\n // literal-path rules, because version-2 snapshots already sit in archived\n // features and travel with them across loam upgrades. Version 1 stays\n // refused: it records nothing about its restore source.\n if (typeof parsed.version !== \"number\") return null;\n if (parsed.version !== SNAPSHOT_VERSION && parsed.version !== SNAPSHOT_COMPAT_VERSION) return null;\n if (parsed.feature !== featureId || parsed.dirName !== dirName) return null;\n if (typeof parsed.archivedAt !== \"string\" || !isCanonicalIsoDate(parsed.archivedAt)) return null;\n if (!Array.isArray(parsed.files)) return null;\n\n const seen = new Set<string>();\n const files: ValidatedSnapshotEntry[] = [];\n for (const raw of parsed.files) {\n if (!isRecord(raw)) return null;\n if (typeof raw.path !== \"string\" || typeof raw.existed !== \"boolean\") return null;\n if (typeof raw.after !== \"string\" || !/^[0-9a-f]{64}$/.test(raw.after)) return null;\n // A manifest that claims a pre-image must say what it should hash to, and\n // one that claims none must not. Either way it is a shape question, so it\n // is answered here with the rest of them; whether the bytes MATCH is a\n // different answer with its own code (`snapshot-corrupt`).\n if (raw.existed ? typeof raw.before !== \"string\" || !/^[0-9a-f]{64}$/.test(raw.before) : raw.before !== null) {\n return null;\n }\n const before = raw.existed ? (raw.before as string) : null;\n if (seen.has(raw.path)) return null;\n seen.add(raw.path);\n\n // The version-3 shape rule, exact in both directions: a `services/` row\n // carries its (service, artifact) key — that key is what version 3 IS,\n // and a row without one is not a manifest this loam wrote — while a row\n // anywhere else carries none, because nothing outside `services/` moves.\n // Version-2 rows never carry the fields and are not asked to.\n const key = readServiceKey(parsed.version, raw);\n if (key === undefined) return null;\n\n // Where the file lives NOW. A re-keyed row resolves through the current\n // enumeration — the whole point of the bump: the bytes land wherever the\n // service lives today, not where it lived on the day of the archive. An\n // id the enumeration does not answer falls back to the literal path\n // (an absent service restores as a create, exactly as before).\n let targetRel = raw.path;\n if (key !== null) {\n const current = request.serviceDirOf(key.service);\n if (current !== null) targetRel = `${current}/${key.artifact}`;\n }\n // The realpath test stays on this side, and it is not negotiable: this\n // path is WRITTEN through. A symlink that leaves the repo is\n // indistinguishable, lexically, from a service directory the operator\n // mounted on purpose — `escape/owned.txt` and `services/<svc>/spec.md`\n // are both \"inside docsDir\" until something resolves them — so the only\n // check that can refuse the first is the one that also refuses the\n // second. Restoring is the write; `staging.ts` only reads and compares a\n // digest, which is why its resolution of this same field can be lexical\n // and this one cannot.\n const target = resolvePortableFileInside(docsDir, targetRel, `snapshot path '${raw.path}'`);\n let snapshot: string | null = null;\n const snapshotRel = `${SNAPSHOT_DIR}/files/${raw.path}`;\n if (raw.existed) {\n // Resolve from the feature directory, which always exists. The `files/`\n // directory legitimately does not exist when every archive write was a\n // creation; anchoring here permits that case while still inspecting an\n // existing `files` component for symlink escape.\n snapshot = resolvePortableFileInside(featureDir, snapshotRel, `snapshot pre-image '${raw.path}'`);\n // Archive writes plain files. A missing pre-image or a symlink (even an\n // internally-contained one) is not the byte snapshot this manifest\n // claims, so refuse before any destination is staged.\n if (!existsSync(snapshot) || !lstatSync(snapshot).isFile()) return null;\n } else {\n const unexpected = resolvePortableFileInside(featureDir, snapshotRel, `snapshot pre-image '${raw.path}'`);\n if (existsSync(unexpected)) return null;\n }\n files.push({ path: raw.path, existed: raw.existed, after: raw.after, before, target, snapshot });\n }\n\n return {\n // The version as WRITTEN, not as preferred: a caller looking at a\n // validated manifest deserves to know which rules it was read under.\n version: parsed.version,\n feature: featureId,\n dirName,\n archivedAt: parsed.archivedAt,\n files,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * The (service, artifact) key of one manifest row, under its version's shape\n * rule. Three answers: the key; null when the row legitimately carries none (a\n * non-`services/` row, or any version-2 row — the key postdates version 2);\n * undefined when the row breaks the rule and the manifest must be refused\n * whole, because a half-keyed or wrongly-keyed row is not a record this loam\n * wrote and nothing can say what else in the file to distrust.\n */\nfunction readServiceKey(\n version: number,\n raw: Record<string, unknown>,\n): { service: string; artifact: string } | null | undefined {\n if (version !== SNAPSHOT_VERSION) return null;\n const under = typeof raw.path === \"string\" && raw.path.startsWith(\"services/\");\n if (!under) return raw.service === undefined && raw.artifact === undefined ? null : undefined;\n // The id is a lookup key into the enumeration and the artifact is joined\n // under the resolved directory, so the id must be a single path segment; the\n // artifact's own containment is `resolvePortableFileInside`'s question.\n if (typeof raw.service !== \"string\" || raw.service.length === 0 || raw.service.includes(\"/\")) return undefined;\n if (typeof raw.artifact !== \"string\" || raw.artifact.length === 0) return undefined;\n return { service: raw.service, artifact: raw.artifact };\n}\n\nexport function isCanonicalIsoDate(value: string): boolean {\n const millis = Date.parse(value);\n return Number.isFinite(millis) && new Date(millis).toISOString() === value;\n}\n\n/** Remove `dir` and each empty ancestor, stopping short of `stopAt`. Best effort. */\n"]}
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/commands/unarchive/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The snapshot manifest, validated before a single byte is written back.\n *\n * `unarchive` says \"I put your docs back\", and that sentence must never be\n * sayable over a pre-image edited in the archived feature directory since. Each\n * entry carries the digest of what archive WROTE and the digest of what will be\n * RESTORED, and this is where both are checked — version, timestamp, feature\n * name, duplicate paths and all — so the restore below it can assume the file\n * describes the archive it claims to.\n */\nimport { existsSync, lstatSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { resolveInside, resolvePortableFileInside } from \"../../core/kernel/path-safety.js\";\nimport { isRecord } from \"../../core/kernel/records.js\";\nimport {\n SNAPSHOT_COMPAT_VERSION,\n SNAPSHOT_DIR,\n SNAPSHOT_MANIFEST,\n SNAPSHOT_VERSION,\n type SnapshotManifest,\n} from \"../../core/staging/snapshot/manifest.js\";\n\nexport interface ValidatedSnapshotEntry {\n path: string;\n existed: boolean;\n after: string;\n /** sha256 the pre-image beside the manifest must still have; null when archive created the destination. */\n before: string | null;\n /** Contained destination under the docs repo. */\n target: string;\n /** Contained pre-image, or null when archive created the destination. */\n snapshot: string | null;\n}\n\nexport interface ValidatedSnapshotManifest extends Omit<SnapshotManifest, \"files\"> {\n files: ValidatedSnapshotEntry[];\n}\n\n/**\n * `readManifest`'s inputs, as one object. `serviceDirOf` is the restore-time\n * half of the version-3 re-keying: a service id → its CURRENT docs-relative\n * directory per the enumeration, or null when no such service is enumerated.\n * Injected by the caller (which holds the enumeration) rather than resolved\n * here, so this module keeps validating one file against one repo state.\n */\nexport interface ManifestRequest {\n featureDir: string;\n docsDir: string;\n featureId: string;\n dirName: string;\n serviceDirOf: (service: string) => string | null;\n}\n\n/**\n * The snapshot manifest, or null when there is nothing this command can honour —\n * absent, unreadable, or a layout version it would have to guess at. All three are\n * the same answer to the caller: this feature cannot be unarchived automatically.\n */\nexport async function readManifest(request: ManifestRequest): Promise<ValidatedSnapshotManifest | null> {\n const { featureDir, docsDir, featureId, dirName } = request;\n try {\n const path = resolveInside(\n featureDir,\n join(SNAPSHOT_DIR, SNAPSHOT_MANIFEST),\n \"snapshot manifest path\",\n );\n if (!existsSync(path)) return null;\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as unknown;\n if (!isRecord(parsed)) return null;\n // Version 3 is what archive writes; version 2 is read forever under its own\n // literal-path rules, because version-2 snapshots already sit in archived\n // features and travel with them across loam upgrades. Version 1 stays\n // refused: it records nothing about its restore source.\n if (typeof parsed.version !== \"number\") return null;\n if (parsed.version !== SNAPSHOT_VERSION && parsed.version !== SNAPSHOT_COMPAT_VERSION) return null;\n if (parsed.feature !== featureId || parsed.dirName !== dirName) return null;\n if (typeof parsed.archivedAt !== \"string\" || !isCanonicalIsoDate(parsed.archivedAt)) return null;\n if (!Array.isArray(parsed.files)) return null;\n\n const seen = new Set<string>();\n const files: ValidatedSnapshotEntry[] = [];\n for (const raw of parsed.files) {\n if (!isRecord(raw)) return null;\n if (typeof raw.path !== \"string\" || typeof raw.existed !== \"boolean\") return null;\n if (typeof raw.after !== \"string\" || !/^[0-9a-f]{64}$/.test(raw.after)) return null;\n // A manifest that claims a pre-image must say what it should hash to, and\n // one that claims none must not. Either way it is a shape question, so it\n // is answered here with the rest of them; whether the bytes MATCH is a\n // different answer with its own code (`snapshot-corrupt`).\n if (raw.existed ? typeof raw.before !== \"string\" || !/^[0-9a-f]{64}$/.test(raw.before) : raw.before !== null) {\n return null;\n }\n const before = raw.existed ? (raw.before as string) : null;\n if (seen.has(raw.path)) return null;\n seen.add(raw.path);\n\n // The version-3 shape rule, exact in both directions: a `services/` row\n // carries its (service, artifact) key — that key is what version 3 IS,\n // and a row without one is not a manifest this loam wrote — while a row\n // anywhere else carries none, because nothing outside `services/` moves.\n // Version-2 rows never carry the fields and are not asked to.\n const key = readServiceKey(parsed.version, raw);\n if (key === undefined) return null;\n\n // Where the file lives NOW. A re-keyed row resolves through the current\n // enumeration — the whole point of the bump: the bytes land wherever the\n // service lives today, not where it lived on the day of the archive. An\n // id the enumeration does not answer falls back to the literal path\n // (an absent service restores as a create, exactly as before).\n let targetRel = raw.path;\n if (key !== null) {\n const current = request.serviceDirOf(key.service);\n if (current !== null) targetRel = `${current}/${key.artifact}`;\n }\n // The realpath test stays on this side, and it is not negotiable: this\n // path is WRITTEN through. A symlink that leaves the repo is\n // indistinguishable, lexically, from a service directory the operator\n // mounted on purpose — `escape/owned.txt` and `services/<svc>/spec.md`\n // are both \"inside docsDir\" until something resolves them — so the only\n // check that can refuse the first is the one that also refuses the\n // second. Restoring is the write; `staging.ts` only reads and compares a\n // digest, which is why its resolution of this same field can be lexical\n // and this one cannot.\n const target = resolvePortableFileInside(docsDir, targetRel, `snapshot path '${raw.path}'`);\n let snapshot: string | null = null;\n const snapshotRel = `${SNAPSHOT_DIR}/files/${raw.path}`;\n if (raw.existed) {\n // Resolve from the feature directory, which always exists. The `files/`\n // directory legitimately does not exist when every archive write was a\n // creation; anchoring here permits that case while still inspecting an\n // existing `files` component for symlink escape.\n snapshot = resolvePortableFileInside(featureDir, snapshotRel, `snapshot pre-image '${raw.path}'`);\n // Archive writes plain files. A missing pre-image or a symlink (even an\n // internally-contained one) is not the byte snapshot this manifest\n // claims, so refuse before any destination is staged.\n if (!existsSync(snapshot) || !lstatSync(snapshot).isFile()) return null;\n } else {\n const unexpected = resolvePortableFileInside(featureDir, snapshotRel, `snapshot pre-image '${raw.path}'`);\n if (existsSync(unexpected)) return null;\n }\n files.push({ path: raw.path, existed: raw.existed, after: raw.after, before, target, snapshot });\n }\n\n return {\n // The version as WRITTEN, not as preferred: a caller looking at a\n // validated manifest deserves to know which rules it was read under.\n version: parsed.version,\n feature: featureId,\n dirName,\n archivedAt: parsed.archivedAt,\n files,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * The (service, artifact) key of one manifest row, under its version's shape\n * rule. Three answers: the key; null when the row legitimately carries none (a\n * non-`services/` row, or any version-2 row — the key postdates version 2);\n * undefined when the row breaks the rule and the manifest must be refused\n * whole, because a half-keyed or wrongly-keyed row is not a record this loam\n * wrote and nothing can say what else in the file to distrust.\n */\nfunction readServiceKey(\n version: number,\n raw: Record<string, unknown>,\n): { service: string; artifact: string } | null | undefined {\n if (version !== SNAPSHOT_VERSION) return null;\n const under = typeof raw.path === \"string\" && raw.path.startsWith(\"services/\");\n if (!under) return raw.service === undefined && raw.artifact === undefined ? null : undefined;\n // The id is a lookup key into the enumeration and the artifact is joined\n // under the resolved directory, so the id must be a single path segment; the\n // artifact's own containment is `resolvePortableFileInside`'s question.\n if (typeof raw.service !== \"string\" || raw.service.length === 0 || raw.service.includes(\"/\")) return undefined;\n if (typeof raw.artifact !== \"string\" || raw.artifact.length === 0) return undefined;\n return { service: raw.service, artifact: raw.artifact };\n}\n\nexport function isCanonicalIsoDate(value: string): boolean {\n const millis = Date.parse(value);\n return Number.isFinite(millis) && new Date(millis).toISOString() === value;\n}\n\n/** Remove `dir` and each empty ancestor, stopping short of `stopAt`. Best effort. */\n"]}
|
|
@@ -8,19 +8,19 @@
|
|
|
8
8
|
* the digest of what it will RESTORE, and a mismatch on either refuses.
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
|
-
import {
|
|
12
|
-
import { dirname, join, relative } from "node:path";
|
|
13
|
-
import { emitJson, fail, repoPath } from "../../core/envelope/json.js";
|
|
11
|
+
import { readFile, rename } from "node:fs/promises";
|
|
12
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
13
|
+
import { CodedFailure, emitJson, fail, repoPath } from "../../core/envelope/json.js";
|
|
14
14
|
import { archiveDir as archiveRoot, featuresDir as featuresRoot } from "../../core/repo/paths.js";
|
|
15
15
|
import { listServices, resolveFeature } from "../../core/repo/repo.js";
|
|
16
16
|
import { docsRepoState } from "../../core/repo/state.js";
|
|
17
17
|
import { sayRecovery } from "../archive/plan/refusal.js";
|
|
18
|
-
import { quietRm, rollbackError, rollbackStaged, stageWrites, swapStaged, } from "../../core/staging/commit.js";
|
|
18
|
+
import { quietPruneEmptyParents, quietRm, rollbackError, rollbackStaged, stageWrites, swapStaged, } from "../../core/staging/commit.js";
|
|
19
19
|
import { clearCommitIntent, writeCommitIntent } from "../../core/staging/recovery/intent.js";
|
|
20
20
|
import { recoverInterruptedCommit } from "../../core/staging/recovery/recover.js";
|
|
21
|
-
import { SNAPSHOT_DIR } from "../../core/staging/snapshot.js";
|
|
21
|
+
import { SNAPSHOT_DIR } from "../../core/staging/snapshot/manifest.js";
|
|
22
22
|
import { planWrite, sha256 } from "../../core/staging/writes.js";
|
|
23
|
-
import { readManifest
|
|
23
|
+
import { readManifest } from "./manifest.js";
|
|
24
24
|
export async function unarchiveLocked(docsDir, featureId, json, force) {
|
|
25
25
|
// Same first move as archive, under the same lock: a commit killed between two
|
|
26
26
|
// renames is exactly the state an undo must not be computed on top of.
|
|
@@ -148,15 +148,15 @@ export async function unarchiveLocked(docsDir, featureId, json, force) {
|
|
|
148
148
|
const failures = await rollbackStaged(staged);
|
|
149
149
|
await clearCommitIntent(docsDir);
|
|
150
150
|
const wrapped = rollbackError(err, failures);
|
|
151
|
-
throw new
|
|
151
|
+
throw new CodedFailure(failures.length > 0 ? "rollback-incomplete" : "restore-failed", wrapped.message);
|
|
152
152
|
}
|
|
153
153
|
// The snapshot describes an archive that no longer exists; the empty directories
|
|
154
154
|
// are what the merge left behind. Both are tidying — neither can fail the undo.
|
|
155
155
|
await quietRm(join(dest, SNAPSHOT_DIR));
|
|
156
156
|
for (const w of writes)
|
|
157
157
|
if (w.content === null)
|
|
158
|
-
await
|
|
159
|
-
await
|
|
158
|
+
await pruneBelowRoot(docsDir, dirname(w.path));
|
|
159
|
+
await pruneBelowRoot(docsDir, archiveDir);
|
|
160
160
|
const restored = writes.filter((w) => w.content !== null).map((w) => repoPath(docsDir, w.path));
|
|
161
161
|
const removed = writes.filter((w) => w.content === null).map((w) => repoPath(docsDir, w.path));
|
|
162
162
|
if (json) {
|
|
@@ -180,25 +180,14 @@ export async function unarchiveLocked(docsDir, featureId, json, force) {
|
|
|
180
180
|
console.log(" the living docs are back to what they said before the archive.");
|
|
181
181
|
}
|
|
182
182
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
183
|
+
* Remove the empty directories from `dir` upward, stopping BELOW the docs root:
|
|
184
|
+
* never the root itself, nor anything outside it. The walk is staging's own,
|
|
185
|
+
* bounded at the top-level directory `dir` sits in.
|
|
186
186
|
*/
|
|
187
|
-
async function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return;
|
|
193
|
-
try {
|
|
194
|
-
if ((await readdir(cur)).length > 0)
|
|
195
|
-
return;
|
|
196
|
-
await rmdir(cur);
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
cur = dirname(cur);
|
|
202
|
-
}
|
|
187
|
+
async function pruneBelowRoot(docsDir, dir) {
|
|
188
|
+
const top = relative(docsDir, dir).split(sep)[0] ?? "";
|
|
189
|
+
if (top === "" || top.startsWith(".."))
|
|
190
|
+
return;
|
|
191
|
+
await quietPruneEmptyParents(dir, join(docsDir, top));
|
|
203
192
|
}
|
|
204
193
|
//# sourceMappingURL=restore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restore.js","sourceRoot":"","sources":["../../../src/commands/unarchive/restore.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Putting the docs back: read the snapshot, verify every pre-image, then write.\n *\n * The whole restore runs under the docs lock, and the verification is the point\n * of the split — `unarchive` says \"I put your docs back\", and that sentence must\n * never be sayable over a pre-image that was edited in the archived feature\n * directory since. Each entry carries both the digest of what archive WROTE and\n * the digest of what it will RESTORE, and a mismatch on either refuses.\n */\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile, rename, rmdir } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { emitJson, fail, repoPath } from \"../../core/envelope/json.js\";\nimport { archiveDir as archiveRoot, featuresDir as featuresRoot } from \"../../core/repo/paths.js\";\nimport { listServices, resolveFeature } from \"../../core/repo/repo.js\";\nimport { docsRepoState } from \"../../core/repo/state.js\";\nimport { sayRecovery } from \"../archive/plan/refusal.js\";\nimport {\n quietRm,\n rollbackError,\n rollbackStaged,\n stageWrites,\n swapStaged,\n} from \"../../core/staging/commit.js\";\nimport { clearCommitIntent, writeCommitIntent } from \"../../core/staging/recovery/intent.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { SNAPSHOT_DIR } from \"../../core/staging/snapshot.js\";\nimport { planWrite, sha256, type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { readManifest, RestoreFailure } from \"./manifest.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport async function unarchiveLocked(\n docsDir: DocsDir,\n featureId: string,\n json: boolean,\n force: boolean,\n): Promise<void> {\n // Same first move as archive, under the same lock: a commit killed between two\n // renames is exactly the state an undo must not be computed on top of.\n const recovered = await recoverInterruptedCommit(docsDir);\n if (recovered !== null && !json) sayRecovery(recovered);\n\n const featuresDir = featuresRoot(docsDir);\n const archiveDir = archiveRoot(docsDir);\n\n const feature = await resolveFeature(docsDir, featureId, \"only\");\n if (!feature) {\n fail(json, \"unknown-target\", `No archived feature '${featureId}' under ${archiveDir}.`);\n return;\n }\n const dest = join(featuresDir, feature.dirName);\n\n // Refuse to clobber. A feature that is active again carries its own delta, and\n // dropping the archived copy on top of it would bury work in flight. Resolved\n // by the feature's OWN id, not the raw argument — the collision is with\n // whatever answers to this feature's name, however the caller spelled it.\n const active = await resolveFeature(docsDir, feature.id, \"exclude\");\n if (active !== null || existsSync(dest)) {\n const name = active?.dirName ?? feature.dirName;\n fail(\n json,\n \"feature-active\",\n `unarchive ${feature.id} — BLOCKED: features/${name} already exists. Remove or rename it, then re-run.`,\n );\n return;\n }\n\n // The restore-time half of the snapshot's (service, artifact) re-keying: a\n // version-3 entry restores into wherever its service lives TODAY, and the\n // enumeration is the only thing that may say where that is. Built once here,\n // as a map, so the manifest validation stays a pure question of one file\n // against one repo state. \"no-services\" is tolerated the way `listFeatures`\n // tolerates it: a docs repo whose `services/` is gone still restores by the\n // literal paths (every id resolves to null), and it is `doctor`'s job to say\n // the directory is missing — refusing the undo here would add a second,\n // contradictory diagnosis.\n const enumerated = docsRepoState(docsDir).kind === \"ok\" ? await listServices(docsDir) : [];\n const serviceDirs = new Map<string, string>(enumerated.map((s) => [s.id, repoPath(docsDir, s.dir)]));\n const manifest = await readManifest({\n featureDir: feature.dir,\n docsDir,\n featureId: feature.id,\n dirName: feature.dirName,\n serviceDirOf: (service: string): string | null => serviceDirs.get(service) ?? null,\n });\n if (manifest === null) {\n fail(\n json,\n \"snapshot-missing\",\n `unarchive ${feature.id} — BLOCKED: features/archive/${feature.dirName}/${SNAPSHOT_DIR}/ is missing or was written by a different loam. ` +\n `It was archived before loam recorded what it overwrote, and the previous text of a MODIFIED requirement exists nowhere else — ` +\n `restore the living docs from version control instead.`,\n );\n return;\n }\n\n // What archive wrote is recorded, so a file edited since can be told from one\n // this archive is still the top of. Restoring over a later change is silent data\n // loss, so it takes --force to say it was meant.\n const writes: PlannedWrite[] = [];\n const drifted: string[] = [];\n // Pre-images whose bytes are not the bytes archive wrote down. Collected\n // whole, then refused together: \"which files\" is what makes the message\n // actionable, and no destination is staged until every one is accounted for.\n const corrupt: string[] = [];\n for (const entry of manifest.files) {\n const current = existsSync(entry.target) ? await readFile(entry.target) : null;\n const now = current === null ? null : sha256(current);\n // Drift is \"restoring this would destroy something\", so a file already\n // holding its OWN pre-image is not drifted — putting it back writes the\n // same bytes. It is also the state an interrupted restore leaves behind\n // (the repair finishes the swaps it can, then this command re-runs): the\n // narrower test used to call every repaired file a later change and demand\n // `--force` to discard this command's own half-finished undo.\n const restored = entry.existed ? now === entry.before : now === null;\n if (now !== entry.after && !restored) drifted.push(entry.path);\n let before: Buffer | null = null;\n if (entry.snapshot !== null) {\n before = await readFile(entry.snapshot);\n // The one check that was missing entirely. `after` describes what archive\n // WROTE and is checked above; nothing described the bytes it would\n // RESTORE, so a pre-image edited inside the archived feature — a bad\n // merge, a rebase, an editor — was written back verbatim and this command\n // said \"the living docs are back to what they said before the archive\"\n // over text nobody ever wrote.\n if (sha256(before) !== entry.before) corrupt.push(entry.path);\n }\n // A restore whose destination is GONE is a create, and takes the same\n // no-clobber swap an archive's creates take: between the manifest read and\n // the swap, another writer may have put that path back, and a rename would\n // bury it. The same helper both commands use, so neither can decide\n // differently about the same file.\n writes.push(planWrite(entry.target, before));\n }\n if (corrupt.length > 0) {\n fail(\n json,\n \"snapshot-corrupt\",\n `unarchive ${feature.id} — BLOCKED: ${corrupt.length} snapshot pre-image(s) no longer match the digest archive ` +\n `recorded for them, so restoring them would write text nobody authored: ${corrupt.join(\", \")}. ` +\n `--force does not override this — it discards LATER changes to the living docs, and the damage here is to the ` +\n `undo itself. Restore the living docs from version control instead.`,\n );\n return;\n }\n if (drifted.length > 0 && !force) {\n fail(\n json,\n \"snapshot-stale\",\n `unarchive ${feature.id} — BLOCKED: ${drifted.length} file(s) changed since the archive, so this is no longer an undo: ` +\n `${drifted.join(\", \")}. Re-run with --force to restore anyway, discarding those changes.`,\n );\n return;\n }\n\n if (!json) {\n console.log(`unarchive ${feature.id}\\n`);\n if (drifted.length > 0) {\n console.log(`⚠ discarding later changes to ${drifted.length} file(s) (--force):`);\n for (const p of drifted) console.log(` ⚠ ${p}`);\n console.log(\"\");\n }\n }\n\n const staged = await stageWrites(writes);\n try {\n // The same journal archive writes, in the other direction: a restore killed\n // between two renames leaves the docs half-back, and the merged text it was\n // replacing is recorded nowhere — so a repair FINISHES this restore rather\n // than undoing it, from the same digest-checked pre-images.\n await writeCommitIntent(\n docsDir,\n { command: \"unarchive\", restore: \"after\", feature: feature.id, moveFrom: feature.dir, moveTo: dest },\n staged,\n );\n await swapStaged(staged);\n await rename(feature.dir, dest);\n await clearCommitIntent(docsDir);\n } catch (err) {\n // The code is a caller's answer to \"can I trust the repo?\": restore-failed\n // means yes (rolled back), rollback-incomplete means look at it by hand —\n // rollbackError's message lists the files that need one.\n const failures = await rollbackStaged(staged);\n await clearCommitIntent(docsDir);\n const wrapped = rollbackError(err, failures);\n throw new RestoreFailure(failures.length > 0 ? \"rollback-incomplete\" : \"restore-failed\", wrapped.message);\n }\n\n // The snapshot describes an archive that no longer exists; the empty directories\n // are what the merge left behind. Both are tidying — neither can fail the undo.\n await quietRm(join(dest, SNAPSHOT_DIR));\n for (const w of writes) if (w.content === null) await pruneEmptyDirs(dirname(w.path), docsDir);\n await pruneEmptyDirs(archiveDir, docsDir);\n\n const restored = writes.filter((w) => w.content !== null).map((w) => repoPath(docsDir, w.path));\n const removed = writes.filter((w) => w.content === null).map((w) => repoPath(docsDir, w.path));\n\n if (json) {\n emitJson({\n command: \"unarchive\",\n feature: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, dest),\n restored,\n removed,\n discarded: drifted,\n ...(recovered === null ? {} : { recovered }),\n });\n return;\n }\n for (const p of restored) console.log(` restored: ${p}`);\n for (const p of removed) console.log(` removed: ${p}`);\n console.log(`\\n reopened: features/archive/${feature.dirName} → features/${feature.dirName}`);\n console.log(\" the living docs are back to what they said before the archive.\");\n}\n\n/**\n * The snapshot manifest, or null when there is nothing this command can honour —\n * absent, unreadable, or a layout version it would have to guess at. All three are\n * the same answer to the caller: this feature cannot be unarchived automatically.\n */\n\nasync function pruneEmptyDirs(dir: string, stopAt: string): Promise<void> {\n let cur = dir;\n for (;;) {\n const rel = relative(stopAt, cur);\n if (rel === \"\" || rel.startsWith(\"..\")) return;\n try {\n if ((await readdir(cur)).length > 0) return;\n await rmdir(cur);\n } catch {\n return;\n }\n cur = dirname(cur);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"restore.js","sourceRoot":"","sources":["../../../src/commands/unarchive/restore.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Putting the docs back: read the snapshot, verify every pre-image, then write.\n *\n * The whole restore runs under the docs lock, and the verification is the point\n * of the split — `unarchive` says \"I put your docs back\", and that sentence must\n * never be sayable over a pre-image that was edited in the archived feature\n * directory since. Each entry carries both the digest of what archive WROTE and\n * the digest of what it will RESTORE, and a mismatch on either refuses.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile, rename } from \"node:fs/promises\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport { CodedFailure, emitJson, fail, repoPath } from \"../../core/envelope/json.js\";\nimport { archiveDir as archiveRoot, featuresDir as featuresRoot } from \"../../core/repo/paths.js\";\nimport { listServices, resolveFeature } from \"../../core/repo/repo.js\";\nimport { docsRepoState } from \"../../core/repo/state.js\";\nimport { sayRecovery } from \"../archive/plan/refusal.js\";\nimport {\n quietPruneEmptyParents,\n quietRm,\n rollbackError,\n rollbackStaged,\n stageWrites,\n swapStaged,\n} from \"../../core/staging/commit.js\";\nimport { clearCommitIntent, writeCommitIntent } from \"../../core/staging/recovery/intent.js\";\nimport { recoverInterruptedCommit } from \"../../core/staging/recovery/recover.js\";\nimport { SNAPSHOT_DIR } from \"../../core/staging/snapshot/manifest.js\";\nimport { planWrite, sha256, type PlannedWrite } from \"../../core/staging/writes.js\";\nimport { readManifest } from \"./manifest.js\";\nimport type { DocsDir } from \"../../core/kernel/ids/dirs.js\";\n\nexport async function unarchiveLocked(\n docsDir: DocsDir,\n featureId: string,\n json: boolean,\n force: boolean,\n): Promise<void> {\n // Same first move as archive, under the same lock: a commit killed between two\n // renames is exactly the state an undo must not be computed on top of.\n const recovered = await recoverInterruptedCommit(docsDir);\n if (recovered !== null && !json) sayRecovery(recovered);\n\n const featuresDir = featuresRoot(docsDir);\n const archiveDir = archiveRoot(docsDir);\n\n const feature = await resolveFeature(docsDir, featureId, \"only\");\n if (!feature) {\n fail(json, \"unknown-target\", `No archived feature '${featureId}' under ${archiveDir}.`);\n return;\n }\n const dest = join(featuresDir, feature.dirName);\n\n // Refuse to clobber. A feature that is active again carries its own delta, and\n // dropping the archived copy on top of it would bury work in flight. Resolved\n // by the feature's OWN id, not the raw argument — the collision is with\n // whatever answers to this feature's name, however the caller spelled it.\n const active = await resolveFeature(docsDir, feature.id, \"exclude\");\n if (active !== null || existsSync(dest)) {\n const name = active?.dirName ?? feature.dirName;\n fail(\n json,\n \"feature-active\",\n `unarchive ${feature.id} — BLOCKED: features/${name} already exists. Remove or rename it, then re-run.`,\n );\n return;\n }\n\n // The restore-time half of the snapshot's (service, artifact) re-keying: a\n // version-3 entry restores into wherever its service lives TODAY, and the\n // enumeration is the only thing that may say where that is. Built once here,\n // as a map, so the manifest validation stays a pure question of one file\n // against one repo state. \"no-services\" is tolerated the way `listFeatures`\n // tolerates it: a docs repo whose `services/` is gone still restores by the\n // literal paths (every id resolves to null), and it is `doctor`'s job to say\n // the directory is missing — refusing the undo here would add a second,\n // contradictory diagnosis.\n const enumerated = docsRepoState(docsDir).kind === \"ok\" ? await listServices(docsDir) : [];\n const serviceDirs = new Map<string, string>(enumerated.map((s) => [s.id, repoPath(docsDir, s.dir)]));\n const manifest = await readManifest({\n featureDir: feature.dir,\n docsDir,\n featureId: feature.id,\n dirName: feature.dirName,\n serviceDirOf: (service: string): string | null => serviceDirs.get(service) ?? null,\n });\n if (manifest === null) {\n fail(\n json,\n \"snapshot-missing\",\n `unarchive ${feature.id} — BLOCKED: features/archive/${feature.dirName}/${SNAPSHOT_DIR}/ is missing or was written by a different loam. ` +\n `It was archived before loam recorded what it overwrote, and the previous text of a MODIFIED requirement exists nowhere else — ` +\n `restore the living docs from version control instead.`,\n );\n return;\n }\n\n // What archive wrote is recorded, so a file edited since can be told from one\n // this archive is still the top of. Restoring over a later change is silent data\n // loss, so it takes --force to say it was meant.\n const writes: PlannedWrite[] = [];\n const drifted: string[] = [];\n // Pre-images whose bytes are not the bytes archive wrote down. Collected\n // whole, then refused together: \"which files\" is what makes the message\n // actionable, and no destination is staged until every one is accounted for.\n const corrupt: string[] = [];\n for (const entry of manifest.files) {\n const current = existsSync(entry.target) ? await readFile(entry.target) : null;\n const now = current === null ? null : sha256(current);\n // Drift is \"restoring this would destroy something\", so a file already\n // holding its OWN pre-image is not drifted — putting it back writes the\n // same bytes. It is also the state an interrupted restore leaves behind\n // (the repair finishes the swaps it can, then this command re-runs): the\n // narrower test used to call every repaired file a later change and demand\n // `--force` to discard this command's own half-finished undo.\n const restored = entry.existed ? now === entry.before : now === null;\n if (now !== entry.after && !restored) drifted.push(entry.path);\n let before: Buffer | null = null;\n if (entry.snapshot !== null) {\n before = await readFile(entry.snapshot);\n // The one check that was missing entirely. `after` describes what archive\n // WROTE and is checked above; nothing described the bytes it would\n // RESTORE, so a pre-image edited inside the archived feature — a bad\n // merge, a rebase, an editor — was written back verbatim and this command\n // said \"the living docs are back to what they said before the archive\"\n // over text nobody ever wrote.\n if (sha256(before) !== entry.before) corrupt.push(entry.path);\n }\n // A restore whose destination is GONE is a create, and takes the same\n // no-clobber swap an archive's creates take: between the manifest read and\n // the swap, another writer may have put that path back, and a rename would\n // bury it. The same helper both commands use, so neither can decide\n // differently about the same file.\n writes.push(planWrite(entry.target, before));\n }\n if (corrupt.length > 0) {\n fail(\n json,\n \"snapshot-corrupt\",\n `unarchive ${feature.id} — BLOCKED: ${corrupt.length} snapshot pre-image(s) no longer match the digest archive ` +\n `recorded for them, so restoring them would write text nobody authored: ${corrupt.join(\", \")}. ` +\n `--force does not override this — it discards LATER changes to the living docs, and the damage here is to the ` +\n `undo itself. Restore the living docs from version control instead.`,\n );\n return;\n }\n if (drifted.length > 0 && !force) {\n fail(\n json,\n \"snapshot-stale\",\n `unarchive ${feature.id} — BLOCKED: ${drifted.length} file(s) changed since the archive, so this is no longer an undo: ` +\n `${drifted.join(\", \")}. Re-run with --force to restore anyway, discarding those changes.`,\n );\n return;\n }\n\n if (!json) {\n console.log(`unarchive ${feature.id}\\n`);\n if (drifted.length > 0) {\n console.log(`⚠ discarding later changes to ${drifted.length} file(s) (--force):`);\n for (const p of drifted) console.log(` ⚠ ${p}`);\n console.log(\"\");\n }\n }\n\n const staged = await stageWrites(writes);\n try {\n // The same journal archive writes, in the other direction: a restore killed\n // between two renames leaves the docs half-back, and the merged text it was\n // replacing is recorded nowhere — so a repair FINISHES this restore rather\n // than undoing it, from the same digest-checked pre-images.\n await writeCommitIntent(\n docsDir,\n { command: \"unarchive\", restore: \"after\", feature: feature.id, moveFrom: feature.dir, moveTo: dest },\n staged,\n );\n await swapStaged(staged);\n await rename(feature.dir, dest);\n await clearCommitIntent(docsDir);\n } catch (err) {\n // The code is a caller's answer to \"can I trust the repo?\": restore-failed\n // means yes (rolled back), rollback-incomplete means look at it by hand —\n // rollbackError's message lists the files that need one.\n const failures = await rollbackStaged(staged);\n await clearCommitIntent(docsDir);\n const wrapped = rollbackError(err, failures);\n throw new CodedFailure(failures.length > 0 ? \"rollback-incomplete\" : \"restore-failed\", wrapped.message);\n }\n\n // The snapshot describes an archive that no longer exists; the empty directories\n // are what the merge left behind. Both are tidying — neither can fail the undo.\n await quietRm(join(dest, SNAPSHOT_DIR));\n for (const w of writes) if (w.content === null) await pruneBelowRoot(docsDir, dirname(w.path));\n await pruneBelowRoot(docsDir, archiveDir);\n\n const restored = writes.filter((w) => w.content !== null).map((w) => repoPath(docsDir, w.path));\n const removed = writes.filter((w) => w.content === null).map((w) => repoPath(docsDir, w.path));\n\n if (json) {\n emitJson({\n command: \"unarchive\",\n feature: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, dest),\n restored,\n removed,\n discarded: drifted,\n ...(recovered === null ? {} : { recovered }),\n });\n return;\n }\n for (const p of restored) console.log(` restored: ${p}`);\n for (const p of removed) console.log(` removed: ${p}`);\n console.log(`\\n reopened: features/archive/${feature.dirName} → features/${feature.dirName}`);\n console.log(\" the living docs are back to what they said before the archive.\");\n}\n\n/**\n * Remove the empty directories from `dir` upward, stopping BELOW the docs root:\n * never the root itself, nor anything outside it. The walk is staging's own,\n * bounded at the top-level directory `dir` sits in.\n */\nasync function pruneBelowRoot(docsDir: string, dir: string): Promise<void> {\n const top = relative(docsDir, dir).split(sep)[0] ?? \"\";\n if (top === \"\" || top.startsWith(\"..\")) return;\n await quietPruneEmptyParents(dir, join(docsDir, top));\n}\n"]}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fail, reportNoConfig } from "../../core/envelope/json.js";
|
|
1
|
+
import { CodedFailure, configOrRefuse, fail } from "../../core/envelope/json.js";
|
|
3
2
|
import { message } from "../../core/staging/commit.js";
|
|
4
3
|
import { acquireDocsLock, DocsBusyError } from "../../core/staging/lock.js";
|
|
5
4
|
import { InterruptedCommitError } from "../../core/staging/interrupted.js";
|
|
6
5
|
import { unarchiveLocked } from "./restore.js";
|
|
7
|
-
import { RestoreFailure } from "./manifest.js";
|
|
8
6
|
/**
|
|
9
|
-
*
|
|
10
|
-
* archive's discipline. `restore-failed`
|
|
11
|
-
* yes: nothing was restored, or everything
|
|
12
|
-
* work. `rollback-incomplete` is the code
|
|
13
|
-
* failed AND some files could not be put back
|
|
14
|
-
* Anything ELSE that escapes runUnarchive never
|
|
15
|
-
* the
|
|
7
|
+
* The code a failed unarchive answers with. A commit-phase failure is thrown as
|
|
8
|
+
* a `CodedFailure` — the mirror of archive's discipline. `restore-failed`
|
|
9
|
+
* answers "can I trust the repo?" with yes: nothing was restored, or everything
|
|
10
|
+
* was rolled back, and re-running can work. `rollback-incomplete` is the code
|
|
11
|
+
* that demands a human: the restore failed AND some files could not be put back
|
|
12
|
+
* — the message lists them. Anything ELSE that escapes runUnarchive never
|
|
13
|
+
* touched the commit phase, so it reports as `restore-failed`.
|
|
16
14
|
*/
|
|
15
|
+
function unarchiveErrorCode(err) {
|
|
16
|
+
if (err instanceof CodedFailure)
|
|
17
|
+
return err.code;
|
|
18
|
+
// Another writer holds the docs repo — the lock is taken refuse-fast below.
|
|
19
|
+
if (err instanceof DocsBusyError)
|
|
20
|
+
return "docs-busy";
|
|
21
|
+
if (err instanceof InterruptedCommitError)
|
|
22
|
+
return "commit-interrupted";
|
|
23
|
+
return "restore-failed";
|
|
24
|
+
}
|
|
17
25
|
export function registerUnarchive(program) {
|
|
18
26
|
program
|
|
19
27
|
.command("unarchive")
|
|
@@ -27,36 +35,20 @@ export function registerUnarchive(program) {
|
|
|
27
35
|
await runUnarchive(featureId, json, opts.force === true);
|
|
28
36
|
}
|
|
29
37
|
catch (err) {
|
|
30
|
-
|
|
31
|
-
? err.code
|
|
32
|
-
: err instanceof InterruptedCommitError
|
|
33
|
-
? "commit-interrupted"
|
|
34
|
-
: "restore-failed";
|
|
35
|
-
fail(json, code, `unarchive ${featureId} failed: ${message(err)}`);
|
|
38
|
+
fail(json, unarchiveErrorCode(err), `unarchive ${featureId} failed: ${message(err)}`);
|
|
36
39
|
}
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
42
|
async function runUnarchive(featureId, json, force) {
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
reportNoConfig(json, loaded);
|
|
43
|
+
const config = await configOrRefuse(json);
|
|
44
|
+
if (config === null)
|
|
43
45
|
return;
|
|
44
|
-
}
|
|
45
|
-
const config = loaded.config;
|
|
46
46
|
// The same lock archive takes, for the same reason: unarchive rewrites the
|
|
47
47
|
// living spec, contract and landscape from a snapshot, and an archive landing
|
|
48
48
|
// inside that window would have its merge silently reverted by our restore —
|
|
49
49
|
// or ours by its merge. One writer per docs repo, whichever direction it
|
|
50
50
|
// writes in.
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
release = await acquireDocsLock(config.docsDir);
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
if (err instanceof DocsBusyError)
|
|
57
|
-
throw new RestoreFailure("docs-busy", err.message);
|
|
58
|
-
throw err;
|
|
59
|
-
}
|
|
51
|
+
const release = await acquireDocsLock(config.docsDir);
|
|
60
52
|
try {
|
|
61
53
|
await unarchiveLocked(config.docsDir, featureId, json, force);
|
|
62
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unarchive.js","sourceRoot":"","sources":["../../../src/commands/unarchive/unarchive.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"unarchive.js","sourceRoot":"","sources":["../../../src/commands/unarchive/unarchive.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAkB,MAAM,6BAA6B,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAuB/C;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CAAC,GAAY;IACtC,IAAI,GAAG,YAAY,YAAY;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC;IACjD,4EAA4E;IAC5E,IAAI,GAAG,YAAY,aAAa;QAAE,OAAO,WAAW,CAAC;IACrD,IAAI,GAAG,YAAY,sBAAsB;QAAE,OAAO,oBAAoB,CAAC;IACvE,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,OAAO;SACJ,OAAO,CAAC,WAAW,CAAC;SACpB,QAAQ,CAAC,aAAa,EAAE,2BAA2B,CAAC;SACpD,WAAW,CAAC,6EAA6E,CAAC;SAC1F,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CAAC,SAAS,EAAE,oFAAoF,CAAC;SACvG,MAAM,CAAC,KAAK,EAAE,SAAiB,EAAE,IAAsB,EAAE,EAAE;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAAE,aAAa,SAAS,YAAY,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,SAAiB,EAAE,IAAa,EAAE,KAAc;IAC1E,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO;IAC5B,2EAA2E;IAC3E,8EAA8E;IAC9E,6EAA6E;IAC7E,yEAAyE;IACzE,aAAa;IACb,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,eAAe,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAChE,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import type { Command } from \"commander\";\nimport { CodedFailure, configOrRefuse, fail, type ErrorCode } from \"../../core/envelope/json.js\";\nimport { message } from \"../../core/staging/commit.js\";\nimport { acquireDocsLock, DocsBusyError } from \"../../core/staging/lock.js\";\nimport { InterruptedCommitError } from \"../../core/staging/interrupted.js\";\nimport { unarchiveLocked } from \"./restore.js\";\n\n/**\n * `loam unarchive <FEAT>` — take back a `loam archive`.\n *\n * Not an inverse merge, and it cannot be one. Undoing the requirements axis would\n * mean restoring a MODIFIED requirement's PREVIOUS text, which appears nowhere:\n * the delta records what the requirement became, never what it was. The landscape\n * axis is no better — the merge drops the feature tags, so the added lines are\n * indistinguishable from lines that were always there. Anything reconstructed\n * from the archived feature would be a plausible guess at the old docs, which is\n * exactly the kind of quiet fiction the rest of loam exists to prevent.\n *\n * So archive writes the bytes down instead (`<feature>/.loam-before/`), and this\n * command puts them back. The snapshot is optional on restore: a feature archived\n * before it existed fails with a clear message rather than a crash.\n */\n\ninterface UnarchiveOptions {\n json?: boolean;\n force?: boolean;\n}\n\n/**\n * The code a failed unarchive answers with. A commit-phase failure is thrown as\n * a `CodedFailure` — the mirror of archive's discipline. `restore-failed`\n * answers \"can I trust the repo?\" with yes: nothing was restored, or everything\n * was rolled back, and re-running can work. `rollback-incomplete` is the code\n * that demands a human: the restore failed AND some files could not be put back\n * — the message lists them. Anything ELSE that escapes runUnarchive never\n * touched the commit phase, so it reports as `restore-failed`.\n */\nfunction unarchiveErrorCode(err: unknown): ErrorCode {\n if (err instanceof CodedFailure) return err.code;\n // Another writer holds the docs repo — the lock is taken refuse-fast below.\n if (err instanceof DocsBusyError) return \"docs-busy\";\n if (err instanceof InterruptedCommitError) return \"commit-interrupted\";\n return \"restore-failed\";\n}\n\nexport function registerUnarchive(program: Command): void {\n program\n .command(\"unarchive\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"Take back a `loam archive`: restore the living docs and re-open the feature\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--force\", \"restore even if the living docs changed after the archive (discards those changes)\")\n .action(async (featureId: string, opts: UnarchiveOptions) => {\n const json = opts.json === true;\n try {\n await runUnarchive(featureId, json, opts.force === true);\n } catch (err) {\n fail(json, unarchiveErrorCode(err), `unarchive ${featureId} failed: ${message(err)}`);\n }\n });\n}\n\nasync function runUnarchive(featureId: string, json: boolean, force: boolean): Promise<void> {\n const config = await configOrRefuse(json);\n if (config === null) return;\n // The same lock archive takes, for the same reason: unarchive rewrites the\n // living spec, contract and landscape from a snapshot, and an archive landing\n // inside that window would have its merge silently reverted by our restore —\n // or ours by its merge. One writer per docs repo, whichever direction it\n // writes in.\n const release = await acquireDocsLock(config.docsDir);\n try {\n await unarchiveLocked(config.docsDir, featureId, json, force);\n } finally {\n await release();\n }\n}\n"]}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* one module is what stops "new" being decided differently in three places.
|
|
16
16
|
*/
|
|
17
17
|
import { existsSync } from "node:fs";
|
|
18
|
-
import {
|
|
18
|
+
import {} from "../../core/c4/likec4.js";
|
|
19
19
|
import { elementService, serviceResolver } from "../../core/c4/resolve/service.js";
|
|
20
20
|
import {} from "../../core/kernel/ids/service.js";
|
|
21
21
|
import { landscapePath as landscapeFile } from "../../core/repo/paths.js";
|
|
@@ -31,11 +31,6 @@ import { coversEntries, coversUnknownFindings } from "./checks/requirements.js";
|
|
|
31
31
|
export async function deltaArchCoverage(delta) {
|
|
32
32
|
const { docsDir, elements, relationships, taggedEls, taggedRels, archDeltas, featureServices, scenarioText, preloadedLand, fleet, } = delta;
|
|
33
33
|
const findings = [];
|
|
34
|
-
// The read index the service models below are read through. A feature target
|
|
35
|
-
// may arrive without one (`validate --feature` builds no fleet), and it gets
|
|
36
|
-
// its own for `validateService`'s reason: `serviceModel` needs the enumeration
|
|
37
|
-
// and a document memo to answer at all.
|
|
38
|
-
const context = fleet ?? new FleetContext();
|
|
39
34
|
// The delta's own element→service resolver, built once for the whole feature.
|
|
40
35
|
// `serviceOf` is a one-shot wrapper that rebuilds its index on every call, and
|
|
41
36
|
// the two loops below ask it up to five times per tagged edge — so a delta
|
|
@@ -71,9 +66,9 @@ export async function deltaArchCoverage(delta) {
|
|
|
71
66
|
// this is where agent-built code cuts its corners — the outbox, the retries,
|
|
72
67
|
// the alerts — because no business scenario was ever going to mention them.
|
|
73
68
|
// Grouping-only elements take the same exemptions the fleet map applies
|
|
74
|
-
// (person kinds, #external) — one set, in
|
|
75
|
-
// service" cannot mean two things. Warnings, never archive gates;
|
|
76
|
-
// escalates.
|
|
69
|
+
// (person kinds, #external) — one set, in core/vocabulary/maturity.ts, so
|
|
70
|
+
// "not a service" cannot mean two things. Warnings, never archive gates;
|
|
71
|
+
// `--strict` escalates.
|
|
77
72
|
//
|
|
78
73
|
// Only requirements the archive will MERGE grant coverage here. In a delta,
|
|
79
74
|
// BASE means "the living state, quoted": it merges nothing, emits no
|
|
@@ -93,11 +88,7 @@ export async function deltaArchCoverage(delta) {
|
|
|
93
88
|
const livingLandscape = async () => {
|
|
94
89
|
if (living === undefined) {
|
|
95
90
|
const lp = landscapeFile(docsDir);
|
|
96
|
-
living = existsSync(lp)
|
|
97
|
-
? fleet === undefined
|
|
98
|
-
? await loadFile(lp)
|
|
99
|
-
: await fleet.loadLikeC4(lp)
|
|
100
|
-
: null;
|
|
91
|
+
living = existsSync(lp) ? await fleet.loadLikeC4(lp) : null;
|
|
101
92
|
}
|
|
102
93
|
return living;
|
|
103
94
|
};
|
|
@@ -183,7 +174,7 @@ export async function deltaArchCoverage(delta) {
|
|
|
183
174
|
// convicted as a typo. `mapUnreadable` is the same silence for the same
|
|
184
175
|
// reason: nothing may widen a scope out of a document nobody could read.
|
|
185
176
|
const svcPaths = await locateServicePaths(docsDir, svc, fleet);
|
|
186
|
-
const model = existsSync(svcPaths.model) ? await
|
|
177
|
+
const model = existsSync(svcPaths.model) ? await fleet.serviceModel(docsDir, svcPaths) : null;
|
|
187
178
|
if (model !== null && !model.mapUnreadable && model.doc.errors.length === 0) {
|
|
188
179
|
scope = {
|
|
189
180
|
elements: [...baseElements, ...model.doc.elements],
|