@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3448 -0
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +47 -23
- package/QUICKSTART.md +189 -12
- package/README.md +274 -65
- package/ROADMAP.md +572 -31
- package/SCHEMA.md +1709 -155
- package/WORKFLOW.md +298 -72
- 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/adopt/render.js +50 -9
- package/dist/commands/adopt/render.js.map +1 -1
- package/dist/commands/archive/archive.js +16 -19
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
- 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 +77 -43
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +21 -19
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +167 -30
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +14 -19
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +137 -56
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +3 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +134 -30
- 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 +85 -18
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +29 -7
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +102 -14
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +17 -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/doctor.js +60 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +89 -13
- package/dist/commands/explain/explain.js.map +1 -1
- package/dist/commands/explore.js +16 -10
- 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 +5 -7
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +44 -29
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +52 -9
- 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 +37 -34
- 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 +158 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +13 -11
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/feature.js +9 -5
- package/dist/commands/show/feature.js.map +1 -1
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +35 -6
- package/dist/commands/show/marks.js.map +1 -1
- package/dist/commands/show/service.js +41 -33
- package/dist/commands/show/service.js.map +1 -1
- package/dist/commands/show/show.js +17 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +2 -0
- package/dist/commands/status/json.js.map +1 -1
- package/dist/commands/status/print.js +89 -37
- 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 +103 -23
- 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 +106 -46
- 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 +289 -74
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +147 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +200 -21
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +42 -26
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +83 -33
- 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 +78 -27
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +34 -126
- 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/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- 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 +11 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +26 -20
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +37 -13
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +144 -126
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/load.js +66 -0
- package/dist/commands/validate/fleet/load.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +44 -14
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +10 -2
- 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/place.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +50 -15
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +40 -47
- 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/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +36 -160
- 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 +21 -107
- 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 +36 -14
- 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/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +121 -61
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +88 -28
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +38 -3
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +85 -60
- 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 +55 -52
- 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 +81 -106
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +21 -9
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
- 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 +24 -8
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +80 -8
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +88 -10
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +49 -22
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +39 -6
- 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/protocol.js +1 -1
- package/dist/core/agent/protocol.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/support/report.js +46 -5
- package/dist/core/agent/support/report.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 +158 -11
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +78 -39
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +88 -27
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +128 -13
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +96 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +106 -4
- 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/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +6 -3
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +68 -27
- 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 +32 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +33 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +12 -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 +96 -36
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/checks.js +138 -3
- package/dist/core/brief/checks.js.map +1 -1
- package/dist/core/brief/landscape.js +209 -130
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +109 -31
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +14 -3
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/arch.js +90 -182
- package/dist/core/c4/arch.js.map +1 -1
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +169 -14
- 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/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/parsed/view-ids.js +39 -1
- package/dist/core/c4/parsed/view-ids.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +123 -7
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +43 -12
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +79 -43
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +78 -17
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.js.map +1 -0
- package/dist/core/c4/seed/template.js +65 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +297 -10
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +222 -141
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +79 -13
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/contract.js +20 -0
- package/dist/core/c4/splice/contract.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +20 -20
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/identity/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +37 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -1
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +182 -114
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +160 -56
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +13 -19
- 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/delta/uncovered.js +2 -2
- package/dist/core/capabilities/delta/uncovered.js.map +1 -1
- package/dist/core/capabilities/findings.js +18 -22
- 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/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +103 -22
- 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/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.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 +147 -16
- 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 +28 -87
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.js.map +1 -0
- 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 +51 -6
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/dependencies/fanin.js +2 -2
- package/dist/core/dependencies/fanin.js.map +1 -1
- package/dist/core/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +49 -21
- 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/docs.js +31 -22
- package/dist/core/docs.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/config.js +7 -3
- package/dist/core/doctor/config.js.map +1 -1
- package/dist/core/doctor/doctor.js +91 -11
- package/dist/core/doctor/doctor.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/doctor/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.js.map +1 -0
- package/dist/core/document/apply.js +158 -11
- 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 +26 -21
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/lookup.js +14 -1
- package/dist/core/explain/lookup.js.map +1 -1
- package/dist/core/explain/refusals.js +5 -5
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +68 -109
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +40 -10
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +81 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +11 -4
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +69 -28
- 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/delta.js +10 -7
- package/dist/core/glossary/delta.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/ids/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
- package/dist/core/kernel/path-safety.js +38 -0
- package/dist/core/kernel/path-safety.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 +51 -37
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/links/findings.js.map +1 -1
- package/dist/core/links/parse.js +38 -9
- package/dist/core/links/parse.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 +40 -10
- 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 +301 -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 +28 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +17 -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 +43 -4
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/pack/pack.js +50 -6
- package/dist/core/pack/pack.js.map +1 -1
- package/dist/core/projection/arch-slice.js +2 -2
- package/dist/core/projection/arch-slice.js.map +1 -1
- package/dist/core/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- 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/authored/paths.js +18 -0
- package/dist/core/repo/authored/paths.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +92 -6
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +74 -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 +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +53 -32
- 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/scaffold/landscape.js +121 -13
- package/dist/core/scaffold/landscape.js.map +1 -1
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.js.map +1 -0
- package/dist/core/staging/commit.js +26 -15
- 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 +84 -6
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +24 -14
- package/dist/core/staging/txn/forward.js.map +1 -1
- package/dist/core/staging/writes.js.map +1 -1
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/contracts.js +39 -15
- package/dist/core/status/contracts.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +33 -8
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +24 -17
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/feature/next.js +20 -7
- package/dist/core/status/feature/next.js.map +1 -1
- package/dist/core/status/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +43 -14
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +153 -58
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/scan.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 +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/claims.js +31 -7
- package/dist/core/usecases/delta/claims.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/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +32 -191
- package/dist/core/usecases/delta/overlay.js.map +1 -1
- package/dist/core/usecases/fleet.js +195 -36
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +26 -14
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/place.js.map +1 -1
- package/dist/core/usecases/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/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 +70 -12
- 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 +4 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +75 -4
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/dist/core/vocabulary/report.js +16 -1
- package/dist/core/vocabulary/report.js.map +1 -1
- package/examples/README.md +54 -11
- package/examples/docs/architecture/landscape.likec4 +62 -6
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +7 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
- 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/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/likec4.config.json +0 -1
- package/examples/docs/services/checkout-web/model.likec4 +39 -56
- package/examples/docs/services/order-service/arch.spec.md +13 -6
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +58 -70
- package/examples/docs/services/payment-service/arch.spec.md +7 -4
- package/examples/docs/services/payment-service/model.likec4 +38 -37
- package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/health.yaml +8 -3
- package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -31
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
- package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -220
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -165
- 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 -247
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/repo/tree/views.js +0 -172
- package/dist/core/repo/tree/views.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markers.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/markers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The keys loam adds to a feature's contract that must never reach the living\n * one: `x-loam-remove`, `x-loam-based-on`, and the root `x-loam-baselines`\n * record.\n *\n * They are gathered here rather than spread through the merge because they are\n * the vocabulary both ends of the delta path share — `stripOpenapiRemovalMarkers`\n * takes them out for the projection a service repository sees, and the merge\n * takes them out again on the way into the living document. Two spellings of\n * \"which keys are feature-only\" is how one of the two starts leaking a marker.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { isMap, isScalar, parseDocument } from \"yaml\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport {\n FEATURE_ONLY_KEYS,\n isRemoval,\n operationBaselineOf,\n withoutFeatureKeysDeep,\n withoutOperationBaseline,\n OPENAPI_BASELINE_KEY,\n OPENAPI_BASELINES_KEY,\n} from \"../digest.js\";\nimport { setComponentValue } from \"./components.js\";\nimport { errorMessage, OpenapiMergeError } from \"./error.js\";\n\n/**\n * Remove feature-only operation-removal markers — and the root\n * `x-loam-baselines` record — before a feature contract is used to create a\n * brand-new living document. Normally coherence refuses the removal shape\n * because there cannot be a removal target; this guard also keeps `--approve`\n * from ever persisting a marker into living docs. The record needs the strip\n * on THIS path even more than the pins do: archive's create branch publishes\n * this function's output verbatim, and a components-only delta has nothing\n * strippable inside `paths` at all — the root key must not ride out on the\n * early returns below.\n */\nexport function stripOpenapiRemovalMarkers(featureText: string, service: string): string {\n const feature = parseDocument(featureText);\n if (feature.errors.length > 0) {\n throw new OpenapiMergeError(\"feature\", service, feature.errors[0]!.message);\n }\n let plain: unknown;\n try {\n plain = feature.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n const hasRecord = isRecord(plain) && OPENAPI_BASELINES_KEY in plain;\n // Shape from the RESOLVED tree, for the same reason the merge reads it there:\n // an aliased `paths` or path item is not a map node, and treating it as\n // \"nothing to strip\" is how a feature-only marker reached a living contract.\n const paths = plainChild(plain, \"paths\");\n if (!isRecord(paths)) {\n if (!hasRecord) return featureText;\n feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return serialize(feature, service);\n }\n\n const cleaned: Record<string, unknown> = {};\n let stripped = false;\n for (const [path, item] of Object.entries(paths)) {\n const kept = withoutFeatureMarkers(item);\n if (!isDeepStrictEqual(kept, item)) stripped = true;\n // A path the strip emptied goes with its last operation: `\\/x: {}` is a\n // path the contract advertises and nothing answers.\n if (kept !== undefined) cleaned[path] = kept;\n }\n // Component values get the same discipline as path items: a component\n // holding an operation shape (a `pathItems` component, a callback) nests\n // the feature-only keys where the paths walk never looks, and the create\n // branch publishes this function's output verbatim. The write is\n // string-key-matched (setComponentValue) so a numeric component name is\n // replaced, never duplicated.\n const comps = plainChild(plain, \"components\");\n if (isRecord(comps)) {\n for (const [kind, names] of Object.entries(comps)) {\n if (!isRecord(names)) continue;\n for (const [name, value] of Object.entries(names)) {\n const kept = withoutFeatureKeysDeep(value);\n if (kept !== value) {\n setComponentValue(feature, kind, name, kept);\n stripped = true;\n }\n }\n }\n }\n\n if (!stripped) {\n if (!hasRecord) return featureText;\n feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return serialize(feature, service);\n }\n\n // Edit the AST in place when every node the strip touches IS a node — the\n // author's comments, key order and formatting are theirs to keep. An alias\n // cannot be edited in place (there is one shared value behind it, and\n // deleting a key through it would change every use), so those documents get\n // their `paths` rewritten from the resolved tree instead: losing formatting\n // is a cost, shipping the marker is a corruption.\n //\n // The test has to reach the OPERATION nodes too, not just the path items. The\n // in-place branch strips a surviving operation's pin through\n // `[\"paths\", p, m, x-loam-based-on]`, and an aliased operation is exactly one\n // level below where the check used to look — so `deleteIn` walked into a node\n // that is not a collection and threw out of archive as `internal`, on a\n // document whose only fault was writing one operation twice.\n //\n // What disqualifies a method is \"there is something here to strip and I cannot\n // reach it\", not \"this is not a map node\". Only a resolved MAPPING can be a\n // removal marker or carry a pin, so a method spelled as a scalar — `get: ~`,\n // or a string — is nothing the in-place branch would have touched; demanding a\n // map node of it too demoted whole documents to the rewrite branch and cost\n // their author every comment, anchor and hand-chosen key order inside `paths`,\n // in the bytes archive installs as the living contract. What is left is the\n // real hazard: a mapping the AST does not hand back as a map. An alias is the\n // one node kind that resolves to a mapping without being one, and a method\n // whose KEY is an alias is not findable by name at all, so `deleteIn` reports\n // nothing deleted and the marker ships in silence. Both answer `undefined` or\n // a non-map here, and both belong on the safe branch.\n const editable =\n isMap(feature.get(\"paths\")) &&\n Object.entries(paths).every(\n ([p, item]) =>\n isMap(feature.getIn([\"paths\", p])) &&\n (!isRecord(item) ||\n Object.entries(item).every(\n ([m, op]) => !HTTP_METHODS.has(m) || !isRecord(op) || isMap(feature.getIn([\"paths\", p, m])),\n )),\n );\n if (!editable) {\n feature.setIn([\"paths\"], cleaned);\n } else {\n for (const [path, item] of Object.entries(paths)) {\n if (!isRecord(item)) continue;\n for (const [m, value] of Object.entries(item)) {\n // A loam marker at PATH level, deleted here for the same reason the\n // resolved-tree branch drops it: the two branches must agree exactly\n // about what a living contract may carry.\n if (FEATURE_ONLY_KEYS.has(m)) feature.deleteIn([\"paths\", path, m]);\n if (!HTTP_METHODS.has(m)) continue;\n if (isRemoval(value)) feature.deleteIn([\"paths\", path, m]);\n // The surviving operations keep their formatting but lose their pin:\n // the in-place branch must strip everything the resolved-tree branch\n // does, or the two disagree about what a living contract may carry.\n else if (operationBaselineOf(value) !== undefined) {\n feature.deleteIn([\"paths\", path, m, OPENAPI_BASELINE_KEY]);\n }\n }\n const remaining = feature.getIn([\"paths\", path]);\n // The merge's twin: no OPERATIONS left means the path goes, surviving\n // path-level keys included — `{}` was only the easy shape of dead.\n if (isMap(remaining) && !remaining.items.some((it) => isScalar(it.key) && HTTP_METHODS.has(String(it.key.value)))) {\n feature.deleteIn([\"paths\", path]);\n }\n }\n }\n // The root record goes on BOTH serialization branches: it is feature\n // bookkeeping exactly like the pins, and the rewrite branch above replaced\n // only `paths`, so the root key survives either way without this.\n if (hasRecord) feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return serialize(feature, service);\n}\n\n/** One spelling of \"hand the document back\", so every strip branch fails the same way. */\nfunction serialize(feature: ReturnType<typeof parseDocument>, service: string): string {\n try {\n return feature.toString();\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n\nexport function opLabel(before: unknown, after: unknown, method: string, path: string): string {\n const operation = operationIdOf(after) ?? operationIdOf(before);\n return operation !== undefined ? `'${operation}' (${method} ${path})` : `${method} ${path}`;\n}\n\n/** Read an operationId from a YAML or plain operation node. */\nexport function operationIdOf(node: unknown): string | undefined {\n const value = isMap(node)\n ? node.get(\"operationId\")\n : node !== null && typeof node === \"object\"\n ? (node as Record<string, unknown>)[\"operationId\"]\n : undefined;\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n\n\n/**\n * Copy a path item without anything that belongs to the FEATURE rather than to\n * the contract: removal-marker operations, the `x-loam-based-on` pins on the\n * operations that survive, and a loam marker written at PATH level. All three\n * are instructions to loam, and a living contract that carries any of them is\n * lying to the next reader — and, for the pin, to the next feature's baseline.\n *\n * The path-level case used to fall through: the filter guarded only HTTP\n * methods, so `x-loam-remove: true` beside the methods was copied verbatim into\n * the fleet's living contract, where it also kept the empty-path cleanup from\n * ever seeing an empty path.\n */\nexport function withoutFeatureMarkers(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([key, value]) => !FEATURE_ONLY_KEYS.has(key) && !(HTTP_METHODS.has(key) && isRemoval(value)))\n .map(([key, value]) => [key, HTTP_METHODS.has(key) ? withoutOperationBaseline(value) : value] as const);\n return entries.length === 0 ? undefined : Object.fromEntries(entries);\n}\n\nexport function plainChild(parent: unknown, key: string): unknown {\n return parent !== null && typeof parent === \"object\"\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"markers.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/markers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The keys loam adds to a feature's contract that must never reach the living\n * one: `x-loam-remove`, `x-loam-based-on`, and the root `x-loam-baselines`\n * record.\n *\n * They are gathered here rather than spread through the merge because they are\n * the vocabulary both ends of the delta path share — `stripOpenapiRemovalMarkers`\n * takes them out for the projection a service repository sees, and the merge\n * takes them out again on the way into the living document. Two spellings of\n * \"which keys are feature-only\" is how one of the two starts leaking a marker.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { isMap, isScalar, type Document } from \"yaml\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport {\n FEATURE_ONLY_KEYS,\n isRemoval,\n operationBaselineOf,\n withoutFeatureKeysDeep,\n withoutOperationBaseline,\n OPENAPI_BASELINE_KEY,\n OPENAPI_BASELINES_KEY,\n} from \"../digest.js\";\nimport { setKeyed } from \"./components.js\";\nimport { openapiRefusal } from \"./error.js\";\nimport { readTree, writeTreeOver } from \"../contract-tree.js\";\n\n/**\n * Remove feature-only operation-removal markers — and the root\n * `x-loam-baselines` record — before a feature contract is used to create a\n * brand-new living document. Normally coherence refuses the removal shape\n * because there cannot be a removal target; this guard also keeps `--approve`\n * from ever persisting a marker into living docs. The record needs the strip\n * on THIS path even more than the pins do: archive's create branch publishes\n * this function's output verbatim, and a components-only delta has nothing\n * strippable inside `paths` at all — the root key must not ride out on the\n * early returns below.\n */\nexport function stripOpenapiRemovalMarkers(featureText: string, service: string): string {\n // One refusal for every branch below, so each fails the same way.\n const fail = openapiRefusal(\"feature\", service);\n const { doc: feature, plain } = readTree(featureText, fail);\n const hasRecord = isRecord(plain) && OPENAPI_BASELINES_KEY in plain;\n // Shape from the RESOLVED tree, for the same reason the merge reads it there:\n // an aliased `paths` or path item is not a map node, and treating it as\n // \"nothing to strip\" is how a feature-only marker reached a living contract.\n const paths = plainChild(plain, \"paths\");\n if (!isRecord(paths)) {\n if (!hasRecord) return featureText;\n feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return writeTreeOver(feature, featureText, fail);\n }\n\n const cleaned: Record<string, unknown> = {};\n let stripped = false;\n for (const [path, item] of Object.entries(paths)) {\n const kept = withoutFeatureMarkers(item);\n if (!isDeepStrictEqual(kept, item)) stripped = true;\n // A path the strip emptied goes with its last operation: `\\/x: {}` is a\n // path the contract advertises and nothing answers.\n if (kept !== undefined) cleaned[path] = kept;\n }\n // Component values get the same discipline as path items: a component\n // holding an operation shape (a `pathItems` component, a callback) nests\n // the feature-only keys where the paths walk never looks, and the create\n // branch publishes this function's output verbatim. The write is\n // string-key-matched (setKeyed) so a numeric component name is\n // replaced, never duplicated.\n const comps = plainChild(plain, \"components\");\n if (isRecord(comps)) {\n for (const [kind, names] of Object.entries(comps)) {\n if (!isRecord(names)) continue;\n for (const [name, value] of Object.entries(names)) {\n const kept = withoutFeatureKeysDeep(value);\n if (kept !== value) {\n setKeyed(feature, [\"components\", kind], name, kept);\n stripped = true;\n }\n }\n }\n }\n\n if (!stripped) {\n if (!hasRecord) return featureText;\n feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return writeTreeOver(feature, featureText, fail);\n }\n\n // Edit the AST in place when every node the strip touches IS a node — the\n // author's comments, key order and formatting are theirs to keep. An alias\n // cannot be edited in place (there is one shared value behind it, and\n // deleting a key through it would change every use), so those documents get\n // their `paths` rewritten from the resolved tree instead: losing formatting\n // is a cost, shipping the marker is a corruption.\n //\n // The test has to reach the OPERATION nodes too, not just the path items. The\n // in-place branch strips a surviving operation's pin through\n // `[\"paths\", p, m, x-loam-based-on]`, and an aliased operation is exactly one\n // level below where the check used to look — so `deleteIn` walked into a node\n // that is not a collection and threw out of archive as `internal`, on a\n // document whose only fault was writing one operation twice.\n //\n // What disqualifies a method is \"there is something here to strip and I cannot\n // reach it\", not \"this is not a map node\". Only a resolved MAPPING can be a\n // removal marker or carry a pin, so a method spelled as a scalar — `get: ~`,\n // or a string — is nothing the in-place branch would have touched; demanding a\n // map node of it too demoted whole documents to the rewrite branch and cost\n // their author every comment, anchor and hand-chosen key order inside `paths`,\n // in the bytes archive installs as the living contract. What is left is the\n // real hazard: a mapping the AST does not hand back as a map. An alias is the\n // one node kind that resolves to a mapping without being one, and a method\n // whose KEY is an alias is not findable by name at all, so `deleteIn` reports\n // nothing deleted and the marker ships in silence. Both answer `undefined` or\n // a non-map here, and both belong on the safe branch.\n const editable =\n isMap(feature.get(\"paths\")) &&\n Object.entries(paths).every(\n ([p, item]) =>\n isMap(feature.getIn([\"paths\", p])) &&\n (!isRecord(item) ||\n Object.entries(item).every(\n ([m, op]) => !HTTP_METHODS.has(m) || !isRecord(op) || isMap(feature.getIn([\"paths\", p, m])),\n )),\n );\n if (!editable) {\n feature.setIn([\"paths\"], cleaned);\n } else {\n for (const [path, item] of Object.entries(paths)) {\n if (!isRecord(item)) continue;\n for (const [m, value] of Object.entries(item)) {\n // A loam marker at PATH level, deleted here for the same reason the\n // resolved-tree branch drops it: the two branches must agree exactly\n // about what a living contract may carry.\n if (FEATURE_ONLY_KEYS.has(m)) feature.deleteIn([\"paths\", path, m]);\n if (!HTTP_METHODS.has(m)) continue;\n if (isRemoval(value)) feature.deleteIn([\"paths\", path, m]);\n // The surviving operations keep their formatting but lose their pin:\n // the in-place branch must strip everything the resolved-tree branch\n // does, or the two disagree about what a living contract may carry.\n else if (operationBaselineOf(value) !== undefined) {\n feature.deleteIn([\"paths\", path, m, OPENAPI_BASELINE_KEY]);\n }\n }\n // The merge's twin: no OPERATIONS left means the path goes, surviving\n // path-level keys included — `{}` was only the easy shape of dead.\n dropPathIfNoOperations(feature, path);\n }\n }\n // The root record goes on BOTH serialization branches: it is feature\n // bookkeeping exactly like the pins, and the rewrite branch above replaced\n // only `paths`, so the root key survives either way without this.\n if (hasRecord) feature.deleteIn([OPENAPI_BASELINES_KEY]);\n return writeTreeOver(feature, featureText, fail);\n}\n\n/**\n * Removing the last method leaves a path the contract still advertises and\n * nothing answers — and `{}` is only the easy shape of that state: a surviving\n * path-LEVEL key (`parameters`, `summary`) kept the path alive with zero\n * operations just as misleadingly. No methods left means the path goes,\n * whatever else survived beside them. One cleanup for both sides of the delta\n * path, so they cannot disagree about it: the merge runs it on the living\n * document, the strip on the feature's.\n */\nexport function dropPathIfNoOperations(doc: Document, path: string): void {\n const remaining = doc.getIn([\"paths\", path]);\n if (isMap(remaining) && !remaining.items.some((it) => isScalar(it.key) && HTTP_METHODS.has(String(it.key.value)))) {\n doc.deleteIn([\"paths\", path]);\n }\n}\n\nexport function opLabel(before: unknown, after: unknown, method: string, path: string): string {\n const operation = operationIdOf(after) ?? operationIdOf(before);\n return operation !== undefined ? `'${operation}' (${method} ${path})` : `${method} ${path}`;\n}\n\n/** Read an operationId from a YAML or plain operation node. */\nexport function operationIdOf(node: unknown): string | undefined {\n const value = isMap(node)\n ? node.get(\"operationId\")\n : node !== null && typeof node === \"object\"\n ? (node as Record<string, unknown>)[\"operationId\"]\n : undefined;\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n\n\n/**\n * Copy a path item without anything that belongs to the FEATURE rather than to\n * the contract: removal-marker operations, the `x-loam-based-on` pins on the\n * operations that survive, and a loam marker written at PATH level. All three\n * are instructions to loam, and a living contract that carries any of them is\n * lying to the next reader — and, for the pin, to the next feature's baseline.\n *\n * The path-level case used to fall through: the filter guarded only HTTP\n * methods, so `x-loam-remove: true` beside the methods was copied verbatim into\n * the fleet's living contract, where it also kept the empty-path cleanup from\n * ever seeing an empty path.\n */\nexport function withoutFeatureMarkers(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([key, value]) => !FEATURE_ONLY_KEYS.has(key) && !(HTTP_METHODS.has(key) && isRemoval(value)))\n .map(([key, value]) => [key, HTTP_METHODS.has(key) ? withoutOperationBaseline(value) : value] as const);\n return entries.length === 0 ? undefined : Object.fromEntries(entries);\n}\n\nexport function plainChild(parent: unknown, key: string): unknown {\n return parent !== null && typeof parent === \"object\"\n ? (parent as Record<string, unknown>)[key]\n : undefined;\n}\n"]}
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
* survives a refactor.
|
|
10
10
|
*/
|
|
11
11
|
import { isDeepStrictEqual } from "node:util";
|
|
12
|
-
import { isMap
|
|
12
|
+
import { isMap } from "yaml";
|
|
13
13
|
// Every `isRecord` below asks one question of a resolved plain tree: is this a
|
|
14
14
|
// node that can hold OpenAPI keys — an object, not an array?
|
|
15
15
|
import { isRecord } from "../../kernel/records.js";
|
|
16
16
|
import { HTTP_METHODS } from "../doc.js";
|
|
17
|
-
import {
|
|
17
|
+
import { OpenapiMergeError, openapiRefusal } from "./error.js";
|
|
18
|
+
import { readTree, writeTreeOver } from "../contract-tree.js";
|
|
18
19
|
import { classifyOperationBaseline } from "./pin.js";
|
|
19
20
|
import { classifyBaselineDigests, isRemoval, valueDigest } from "../digest.js";
|
|
20
21
|
import { entryFor, readBaselineRecord, restatedSurfaces } from "../baseline/record.js";
|
|
21
|
-
import { opLabel, operationIdOf, plainChild, withoutFeatureMarkers } from "./markers.js";
|
|
22
|
+
import { dropPathIfNoOperations, opLabel, operationIdOf, plainChild, withoutFeatureMarkers } from "./markers.js";
|
|
22
23
|
import { mergeComponentClosure } from "./components.js";
|
|
23
24
|
/**
|
|
24
25
|
* Merge the feature's `paths` into the living OpenAPI structurally (YAML AST, not
|
|
@@ -41,20 +42,8 @@ import { mergeComponentClosure } from "./components.js";
|
|
|
41
42
|
* says so — it is never "nothing to merge".
|
|
42
43
|
*/
|
|
43
44
|
export function mergeOpenapiPaths(livingText, featureText, service) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
throw new OpenapiMergeError("feature", service, feature.errors[0].message);
|
|
47
|
-
}
|
|
48
|
-
let featPlain;
|
|
49
|
-
try {
|
|
50
|
-
// Resolve aliases once with the document's own anchor context. Calling an
|
|
51
|
-
// individual AST node's toJSON() loses that context and can silently turn
|
|
52
|
-
// an aliased operation or component into the wrong value.
|
|
53
|
-
featPlain = feature.toJS() ?? {};
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
throw new OpenapiMergeError("feature", service, errorMessage(error));
|
|
57
|
-
}
|
|
45
|
+
// Aliases resolved once, with the document's own anchor context (readTree).
|
|
46
|
+
const { plain: featPlain } = readTree(featureText, openapiRefusal("feature", service));
|
|
58
47
|
const featPathsPlain = plainChild(featPlain, "paths");
|
|
59
48
|
// The SHAPE refusal is unchanged and stays first: a `paths` that is a
|
|
60
49
|
// sequence or a scalar is a document loam cannot merge. Absent and null
|
|
@@ -77,22 +66,13 @@ export function mergeOpenapiPaths(livingText, featureText, service) {
|
|
|
77
66
|
if (componentsAreTheDelta && !restatedSurfaces(featPlain).some((s) => s.kind === "component")) {
|
|
78
67
|
return noop();
|
|
79
68
|
}
|
|
80
|
-
const living =
|
|
81
|
-
if (living.errors.length > 0) {
|
|
82
|
-
throw new OpenapiMergeError("living", service, living.errors[0].message);
|
|
83
|
-
}
|
|
84
|
-
let livingPlain;
|
|
85
|
-
try {
|
|
86
|
-
livingPlain = living.toJS() ?? {};
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
throw new OpenapiMergeError("living", service, errorMessage(error));
|
|
90
|
-
}
|
|
69
|
+
const { doc: living, plain: livingPlain } = readTree(livingText, openapiRefusal("living", service));
|
|
91
70
|
const livingPathsPlain = plainChild(livingPlain, "paths");
|
|
92
71
|
if (livingPathsPlain !== undefined && livingPathsPlain !== null && !isRecord(livingPathsPlain)) {
|
|
93
72
|
throw new OpenapiMergeError("living", service, "`paths` is not a mapping");
|
|
94
73
|
}
|
|
95
74
|
const modified = [];
|
|
75
|
+
const modifiedOperationIds = new Map();
|
|
96
76
|
const pathItemModified = [];
|
|
97
77
|
const removed = [];
|
|
98
78
|
const quoted = [];
|
|
@@ -147,7 +127,7 @@ export function mergeOpenapiPaths(livingText, featureText, service) {
|
|
|
147
127
|
if (HTTP_METHODS.has(m)) {
|
|
148
128
|
const verdict = classifyOperationBaseline(afterPlain, before === undefined ? undefined : beforePlain);
|
|
149
129
|
// A QUOTE is not a merge input. The author wrote this operation down
|
|
150
|
-
//
|
|
130
|
+
// as a restatement, not because they changed it, so
|
|
151
131
|
// the living contract keeps whatever it holds — including a change
|
|
152
132
|
// that landed after this delta was written. This is mechanical, not a
|
|
153
133
|
// judgement, so `--approve` does not turn it back on: overriding a
|
|
@@ -183,10 +163,16 @@ export function mergeOpenapiPaths(livingText, featureText, service) {
|
|
|
183
163
|
// The difference check covers EVERY key of the path item; only the
|
|
184
164
|
// LABEL depends on whether the key is an HTTP method.
|
|
185
165
|
if (before !== undefined && !isDeepStrictEqual(beforePlain, afterPlain)) {
|
|
186
|
-
if (HTTP_METHODS.has(m))
|
|
187
|
-
|
|
188
|
-
|
|
166
|
+
if (HTTP_METHODS.has(m)) {
|
|
167
|
+
const label = opLabel(beforePlain, afterPlain, m, path);
|
|
168
|
+
modified.push(label);
|
|
169
|
+
const id = operationIdOf(beforePlain) ?? operationIdOf(afterPlain);
|
|
170
|
+
if (id !== undefined)
|
|
171
|
+
modifiedOperationIds.set(label, id);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
189
174
|
pathItemModified.push(`'${m}' (${path})`);
|
|
175
|
+
}
|
|
190
176
|
}
|
|
191
177
|
// Every shape question above was answered from the RESOLVED tree, which
|
|
192
178
|
// is exactly what makes an aliased path item read as an ordinary
|
|
@@ -204,16 +190,10 @@ export function mergeOpenapiPaths(livingText, featureText, service) {
|
|
|
204
190
|
living.setIn(["paths", path, m], publishable[m]);
|
|
205
191
|
written.push({ from: `paths ${path}`, value: publishable[m] });
|
|
206
192
|
}
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
// path goes, whatever else survived beside them. The same cleanup
|
|
212
|
-
// stripOpenapiRemovalMarkers does on the feature side.
|
|
213
|
-
const remaining = living.getIn(["paths", path]);
|
|
214
|
-
if (isMap(remaining) && !remaining.items.some((it) => isScalar(it.key) && HTTP_METHODS.has(String(it.key.value)))) {
|
|
215
|
-
living.deleteIn(["paths", path]);
|
|
216
|
-
}
|
|
193
|
+
// No methods left means the path goes, whatever else survived beside
|
|
194
|
+
// them — the same cleanup stripOpenapiRemovalMarkers does on the
|
|
195
|
+
// feature side, and the reason is spelled once, on the helper.
|
|
196
|
+
dropPathIfNoOperations(living, path);
|
|
217
197
|
continue;
|
|
218
198
|
}
|
|
219
199
|
if (!isRecord(featItemPlain)) {
|
|
@@ -227,15 +207,11 @@ export function mergeOpenapiPaths(livingText, featureText, service) {
|
|
|
227
207
|
}
|
|
228
208
|
const closure = mergeComponentClosure({ living, featPlain, livingPlain, record, written, componentsAreTheDelta });
|
|
229
209
|
const { componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved } = closure;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
throw new OpenapiMergeError("living", service, errorMessage(error));
|
|
236
|
-
}
|
|
210
|
+
// Over the living text, so every entry the merge did not touch keeps the
|
|
211
|
+
// author's bytes (`writeTreeOver` says what re-serializing cost).
|
|
212
|
+
const text = writeTreeOver(living, livingText, openapiRefusal("living", service));
|
|
237
213
|
return {
|
|
238
|
-
text, modified, pathItemModified, removed, quoted, baselineStale,
|
|
214
|
+
text, modified, modifiedOperationIds, pathItemModified, removed, quoted, baselineStale,
|
|
239
215
|
pathItemQuoted, pathItemStale, componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved,
|
|
240
216
|
};
|
|
241
217
|
}
|
|
@@ -248,8 +224,8 @@ export function mergeOpenapiPaths(livingText, featureText, service) {
|
|
|
248
224
|
*/
|
|
249
225
|
function noop() {
|
|
250
226
|
return {
|
|
251
|
-
text: null, modified: [],
|
|
252
|
-
pathItemQuoted: [], pathItemStale: [], componentsModified: [], componentsAdded: [],
|
|
227
|
+
text: null, modified: [], modifiedOperationIds: new Map(), pathItemModified: [], removed: [], quoted: [],
|
|
228
|
+
baselineStale: [], pathItemQuoted: [], pathItemStale: [], componentsModified: [], componentsAdded: [],
|
|
253
229
|
componentsQuoted: [], componentsStale: [], unresolved: [],
|
|
254
230
|
};
|
|
255
231
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/merge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The archive-time merge: a feature's contract folded into the living one, and\n * every condition the caller has to surface afterwards.\n *\n * The result type carries far more than the merged text on purpose. A merge\n * that quietly overwrote an operation, dropped a component, or ran against a\n * stale baseline is not an error — it is a fact the archiving human has to be\n * told, and a return value nobody can forget to read is the only place that\n * survives a refactor.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { isMap, isScalar, parseDocument } from \"yaml\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport { errorMessage, OpenapiMergeError } from \"./error.js\";\nimport { classifyOperationBaseline } from \"./pin.js\";\nimport { classifyBaselineDigests, isRemoval, valueDigest } from \"../digest.js\";\nimport { entryFor, readBaselineRecord, restatedSurfaces } from \"../baseline/record.js\";\nimport { opLabel, operationIdOf, plainChild, withoutFeatureMarkers } from \"./markers.js\";\nimport { mergeComponentClosure } from \"./components.js\";\n\n/** What an OpenAPI path merge computed, including every condition the caller must surface. */\nexport interface OpenapiMergeResult {\n /**\n * The merged living document, or null when the feature document restates\n * nothing at all — neither a path entry nor a component. It used to be null\n * for a document with no `paths` key whatever its components said, and a\n * delta whose whole change was a shared schema archived at exit 0 having\n * merged nothing.\n */\n text: string | null;\n /** Labels of existing operations overwritten with different content. */\n modified: string[];\n /**\n * Labels of PATH-LEVEL keys (`parameters`, `servers`, `summary`, `x-*`)\n * overwritten with different content. They are not operations, so they were\n * excluded from the difference check entirely and overwritten in silence —\n * a delta restating a path with a shorter `parameters` list dropped shared\n * parameters from every operation under it, and the plan said nothing.\n */\n pathItemModified: string[];\n /** Labels of living operations deleted by `x-loam-remove: true` markers. */\n removed: string[];\n /**\n * Labels of operations the delta QUOTED — pinned, and equal to their own\n * baseline — so the merge left the living contract's own copy alone. Reported\n * rather than silent: \"your delta mentions this and I did not write it\" is\n * exactly the sentence whose absence made the revert invisible.\n */\n quoted: string[];\n /**\n * Labels of operations whose pin matches neither the delta's own content nor\n * the living one: edited here AND changed by somebody else in between.\n * `openapi.baseline-stale` refuses these at the gate; the merge still\n * overwrites, because reaching it at all means `--approve` said to.\n */\n baselineStale: string[];\n /** Path-level keys the delta QUOTED (`x-loam-baselines` entry equal to own content) — skipped, living's copy kept. */\n pathItemQuoted: string[];\n /** Path-level keys written on a stale record entry — reaching the merge means `--approve` said to. */\n pathItemStale: string[];\n /** `<kind>/<name>` of living components overwritten with different content. */\n componentsModified: string[];\n /**\n * `<kind>/<name>` of components written into a living contract that did not\n * have them. Beside `componentsModified` because the plan needs a name for\n * what a merge ADDED: a components-only delta merges no operation, and the\n * plan line naming the merged operation ids read `merged ()` for it — empty\n * parentheses, which is how a merge that published a new shared schema\n * looked exactly like one that did nothing.\n */\n componentsAdded: string[];\n /** Components the delta QUOTED — not copied, living's copy kept. */\n componentsQuoted: string[];\n /** Components written on a stale record entry — under `--approve`, like the operations. */\n componentsStale: string[];\n /** Local refs reachable from merged content that resolve in neither document. */\n unresolved: Array<{ ref: string; from: string }>;\n}\n\n\n/**\n * Merge the feature's `paths` into the living OpenAPI structurally (YAML AST, not\n * text splicing). A feature document restating NOTHING — no path entry and no\n * component — is a successful no-op; one whose whole delta is a component is\n * merged like any other, through the closure at the end of this function.\n * The merged operations' local component-ref closure rides along recursively;\n * external refs are left untouched and never gated.\n *\n * Every SHAPE question — is there a `paths` mapping, is this path item a\n * mapping, which methods does it hold — is answered from the RESOLVED plain\n * trees, never from the AST node. Asking the AST (`isMap(node)`) answers a\n * different question: an alias node is not a map even when it resolves to one.\n * `paths: *alias` therefore read as \"no paths to merge\" and the whole contract\n * delta was dropped with a successful exit, and an aliased path ITEM fell into\n * the wholesale-replace branch and deleted every living operation on that path\n * that the alias did not restate. Aliases are legal OpenAPI and the natural way\n * to write a delta that repeats a shape; nothing here may treat them as absent.\n * An alias that cannot be resolved at all is a document loam cannot read, and\n * says so — it is never \"nothing to merge\".\n */\nexport function mergeOpenapiPaths(\n livingText: string,\n featureText: string,\n service: string,\n): OpenapiMergeResult {\n const feature = parseDocument(featureText);\n if (feature.errors.length > 0) {\n throw new OpenapiMergeError(\"feature\", service, feature.errors[0]!.message);\n }\n let featPlain: unknown;\n try {\n // Resolve aliases once with the document's own anchor context. Calling an\n // individual AST node's toJSON() loses that context and can silently turn\n // an aliased operation or component into the wrong value.\n featPlain = feature.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n const featPathsPlain = plainChild(featPlain, \"paths\");\n // The SHAPE refusal is unchanged and stays first: a `paths` that is a\n // sequence or a scalar is a document loam cannot merge. Absent and null\n // (`paths:` with nothing under it) are not shape faults — they are documents\n // holding no path entries, which is a different sentence and now has a\n // different answer.\n if (featPathsPlain !== undefined && featPathsPlain !== null && !isRecord(featPathsPlain)) {\n throw new OpenapiMergeError(\"feature\", service, \"`paths` is not a mapping\");\n }\n const featPathEntries = isRecord(featPathsPlain) ? Object.entries(featPathsPlain) : [];\n // \"Is there anything to merge?\" is a question about the whole delta, and for\n // years it was asked of `paths` alone — an absence test standing in for the\n // decision. It returned here, before the living document was even parsed, so\n // the component closure (the last statement of this function, and the only\n // thing that writes a component) never ran: a feature whose entire change was\n // a shared schema passed the gate and archived at exit 0 having merged\n // NOTHING. The components answer it too now, over the ONE surface\n // enumeration the gate and the rebase plan already grade the delta with.\n const componentsAreTheDelta = featPathEntries.length === 0;\n if (componentsAreTheDelta && !restatedSurfaces(featPlain).some((s) => s.kind === \"component\")) {\n return noop();\n }\n\n const living = parseDocument(livingText);\n if (living.errors.length > 0) {\n throw new OpenapiMergeError(\"living\", service, living.errors[0]!.message);\n }\n let livingPlain: unknown;\n try {\n livingPlain = living.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n const livingPathsPlain = plainChild(livingPlain, \"paths\");\n if (livingPathsPlain !== undefined && livingPathsPlain !== null && !isRecord(livingPathsPlain)) {\n throw new OpenapiMergeError(\"living\", service, \"`paths` is not a mapping\");\n }\n const modified: string[] = [];\n const pathItemModified: string[] = [];\n const removed: string[] = [];\n const quoted: string[] = [];\n const baselineStale: string[] = [];\n const pathItemQuoted: string[] = [];\n const pathItemStale: string[] = [];\n // The surface record, read ONCE for the path-item loop and the component\n // closure. Malformed entries read back as absent — the gate refuses them,\n // and a value nobody can evaluate must never be why a merge skips a write.\n const { record } = readBaselineRecord(featPlain);\n // The plain values this merge actually WRITES, labelled for the ref sweep:\n // the closure copies new components reachable from these, and only these —\n // a quoted operation's refs are living's own business.\n const written: Array<{ from: string; value: unknown }> = [];\n for (const [path, featItemPlain] of featPathEntries) {\n const existing = living.getIn([\"paths\", path]);\n const existingPlain = plainChild(livingPathsPlain, path);\n if (existing !== undefined && !isRecord(existingPlain)) {\n // The living contract holds this path as something other than a path\n // item. Replacing it wholesale is the one branch that may delete living\n // operations without naming them, so it is reserved for paths the living\n // contract does not have at all.\n throw new OpenapiMergeError(\"living\", service, `path '${path}' is not a mapping`);\n }\n if (existing !== undefined) {\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n // EVERY branch publishes through withoutFeatureMarkers. This one used to\n // copy every key of the feature path item and guard only the HTTP\n // methods, so a path-level `x-loam-remove` landed in the living contract\n // with `modified: []` and no warning. A key the strip drops is never\n // written; the removal handling below still reads the RAW item, because\n // that is where the markers it acts on live.\n const publish = withoutFeatureMarkers(featItemPlain);\n const publishable = isRecord(publish) ? publish : {};\n for (const [m, afterPlain] of Object.entries(featItemPlain)) {\n const before = living.getIn([\"paths\", path, m]);\n const beforePlain = plainChild(existingPlain, m);\n if (HTTP_METHODS.has(m) && isRemoval(afterPlain)) {\n // Coherence validates the marker and gates absent/mismatched targets.\n // The merge remains defensive under --approve: never delete a\n // different operation merely because it occupies the requested slot.\n if (\n before !== undefined &&\n operationIdOf(beforePlain) !== undefined &&\n operationIdOf(beforePlain) === operationIdOf(afterPlain)\n ) {\n removed.push(opLabel(beforePlain, afterPlain, m, path));\n living.deleteIn([\"paths\", path, m]);\n }\n continue;\n }\n if (HTTP_METHODS.has(m)) {\n const verdict = classifyOperationBaseline(afterPlain, before === undefined ? undefined : beforePlain);\n // A QUOTE is not a merge input. The author wrote this operation down\n // because the document is complete, not because they changed it, so\n // the living contract keeps whatever it holds — including a change\n // that landed after this delta was written. This is mechanical, not a\n // judgement, so `--approve` does not turn it back on: overriding a\n // gate is a decision, reverting an operation nobody edited is a bug.\n if (verdict === \"quote\") {\n quoted.push(opLabel(beforePlain, afterPlain, m, path));\n continue;\n }\n if (verdict === \"stale\") baselineStale.push(opLabel(beforePlain, afterPlain, m, path));\n }\n if (!(m in publishable)) {\n // A feature-only marker at path level. It is never published, and\n // never reported as a path-item overwrite either — it is not a key of\n // the contract at all. The archive gate names it to the author.\n continue;\n }\n if (!HTTP_METHODS.has(m)) {\n // The operations' verdict, for the keys beside them, from the root\n // record instead of an in-value pin. A QUOTE is skipped for the\n // operations' reason — the author restated it, they did not edit it\n // — and skipping matters MORE here: a path-level key applies to\n // every operation on the path, including ones this feature never\n // mentions. Stale still writes: reaching the merge means --approve.\n const verdict = classifyBaselineDigests(\n entryFor(record, { kind: \"path-item\", path, key: m, value: afterPlain }),\n valueDigest(afterPlain),\n before === undefined ? undefined : valueDigest(beforePlain),\n );\n if (verdict === \"quote\") {\n pathItemQuoted.push(`'${m}' (${path})`);\n continue;\n }\n if (verdict === \"stale\") pathItemStale.push(`'${m}' (${path})`);\n }\n // The difference check covers EVERY key of the path item; only the\n // LABEL depends on whether the key is an HTTP method.\n if (before !== undefined && !isDeepStrictEqual(beforePlain, afterPlain)) {\n if (HTTP_METHODS.has(m)) modified.push(opLabel(beforePlain, afterPlain, m, path));\n else pathItemModified.push(`'${m}' (${path})`);\n }\n // Every shape question above was answered from the RESOLVED tree, which\n // is exactly what makes an aliased path item read as an ordinary\n // mapping. The WRITE cannot be that forgiving: `setIn` walks the AST,\n // and one shared value backs every use of an anchor, so an operation\n // merged through the alias would land in every other path that\n // references it. There is no way to write into one use of an anchor and\n // not the rest, so this is a refusal rather than a fallback — and it is\n // asked here, at the write, because reading is safe and writing is not.\n if (!isMap(living.getIn([\"paths\", path]))) {\n throw new OpenapiMergeError(\n \"living\",\n service,\n `path '${path}' is written as a YAML alias — one shared value backs every use, so merging into it would rewrite every other path that aliases it. Expand it before archiving.`,\n );\n }\n // Without the strip the living contract would grow a pin to a version\n // of itself, and the NEXT feature's baseline would hash it.\n living.setIn([\"paths\", path, m], publishable[m]);\n written.push({ from: `paths ${path}`, value: publishable[m] });\n }\n // Removing the last method leaves a path the contract still advertises\n // and nothing answers — and `{}` is only the easy shape of that state: a\n // surviving path-LEVEL key (`parameters`, `summary`) kept the path alive\n // with zero operations just as misleadingly. No methods left means the\n // path goes, whatever else survived beside them. The same cleanup\n // stripOpenapiRemovalMarkers does on the feature side.\n const remaining = living.getIn([\"paths\", path]);\n if (isMap(remaining) && !remaining.items.some((it) => isScalar(it.key) && HTTP_METHODS.has(String(it.key.value)))) {\n living.deleteIn([\"paths\", path]);\n }\n continue;\n }\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n const clean = withoutFeatureMarkers(featItemPlain);\n if (clean !== undefined) {\n living.setIn([\"paths\", path], clean);\n written.push({ from: `paths ${path}`, value: clean });\n }\n }\n\n const closure = mergeComponentClosure({ living, featPlain, livingPlain, record, written, componentsAreTheDelta });\n const { componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved } = closure;\n\n let text: string;\n try {\n text = living.toString();\n } catch (error) {\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n return {\n text, modified, pathItemModified, removed, quoted, baselineStale,\n pathItemQuoted, pathItemStale, componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved,\n };\n}\n\n/**\n * The successful \"the feature document has nothing to merge\" answer. Every key\n * of the result is spelled here, empty, rather than left off: the two objects\n * are compared field by field in the tests and read field by field by the plan,\n * and a `noop()` that quietly lacked a key would make \"no delta\" and \"a delta\n * that merged nothing\" structurally different shapes of the same answer.\n */\nfunction noop(): OpenapiMergeResult {\n return {\n text: null, modified: [], pathItemModified: [], removed: [], quoted: [], baselineStale: [],\n pathItemQuoted: [], pathItemStale: [], componentsModified: [], componentsAdded: [],\n componentsQuoted: [], componentsStale: [], unresolved: [],\n };\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/merge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The archive-time merge: a feature's contract folded into the living one, and\n * every condition the caller has to surface afterwards.\n *\n * The result type carries far more than the merged text on purpose. A merge\n * that quietly overwrote an operation, dropped a component, or ran against a\n * stale baseline is not an error — it is a fact the archiving human has to be\n * told, and a return value nobody can forget to read is the only place that\n * survives a refactor.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { isMap } from \"yaml\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport { OpenapiMergeError, openapiRefusal } from \"./error.js\";\nimport { readTree, writeTreeOver } from \"../contract-tree.js\";\nimport { classifyOperationBaseline } from \"./pin.js\";\nimport { classifyBaselineDigests, isRemoval, valueDigest } from \"../digest.js\";\nimport { entryFor, readBaselineRecord, restatedSurfaces } from \"../baseline/record.js\";\nimport { dropPathIfNoOperations, opLabel, operationIdOf, plainChild, withoutFeatureMarkers } from \"./markers.js\";\nimport { mergeComponentClosure } from \"./components.js\";\n\n/** What an OpenAPI path merge computed, including every condition the caller must surface. */\nexport interface OpenapiMergeResult {\n /**\n * The merged living document, or null when the feature document restates\n * nothing at all — neither a path entry nor a component. It used to be null\n * for a document with no `paths` key whatever its components said, and a\n * delta whose whole change was a shared schema archived at exit 0 having\n * merged nothing.\n */\n text: string | null;\n /** Labels of existing operations overwritten with different content. */\n modified: string[];\n /**\n * The operationId each `modified` label overwrites, keyed by the label: the\n * LIVING operation's id, or the delta's when the living slot carries none.\n * The living id first because it is the join key consumers name today —\n * `loam archive` asks the fleet's consumer scan with it, to say who the\n * overwrite lands on. A label whose operation carries no id on either side\n * is absent: nothing can name it, so no consumer joins to it by name.\n */\n modifiedOperationIds: ReadonlyMap<string, string>;\n /**\n * Labels of PATH-LEVEL keys (`parameters`, `servers`, `summary`, `x-*`)\n * overwritten with different content. They are not operations, so they were\n * excluded from the difference check entirely and overwritten in silence —\n * a delta restating a path with a shorter `parameters` list dropped shared\n * parameters from every operation under it, and the plan said nothing.\n */\n pathItemModified: string[];\n /** Labels of living operations deleted by `x-loam-remove: true` markers. */\n removed: string[];\n /**\n * Labels of operations the delta QUOTED — pinned, and equal to their own\n * baseline — so the merge left the living contract's own copy alone. Reported\n * rather than silent: \"your delta mentions this and I did not write it\" is\n * exactly the sentence whose absence made the revert invisible.\n */\n quoted: string[];\n /**\n * Labels of operations whose pin matches neither the delta's own content nor\n * the living one: edited here AND changed by somebody else in between.\n * `openapi.baseline-stale` refuses these at the gate; the merge still\n * overwrites, because reaching it at all means `--approve` said to.\n */\n baselineStale: string[];\n /** Path-level keys the delta QUOTED (`x-loam-baselines` entry equal to own content) — skipped, living's copy kept. */\n pathItemQuoted: string[];\n /** Path-level keys written on a stale record entry — reaching the merge means `--approve` said to. */\n pathItemStale: string[];\n /** `<kind>/<name>` of living components overwritten with different content. */\n componentsModified: string[];\n /**\n * `<kind>/<name>` of components written into a living contract that did not\n * have them. Beside `componentsModified` because the plan needs a name for\n * what a merge ADDED: a components-only delta merges no operation, and the\n * plan line naming the merged operation ids read `merged ()` for it — empty\n * parentheses, which is how a merge that published a new shared schema\n * looked exactly like one that did nothing.\n */\n componentsAdded: string[];\n /** Components the delta QUOTED — not copied, living's copy kept. */\n componentsQuoted: string[];\n /** Components written on a stale record entry — under `--approve`, like the operations. */\n componentsStale: string[];\n /** Local refs reachable from merged content that resolve in neither document. */\n unresolved: Array<{ ref: string; from: string }>;\n}\n\n\n/**\n * Merge the feature's `paths` into the living OpenAPI structurally (YAML AST, not\n * text splicing). A feature document restating NOTHING — no path entry and no\n * component — is a successful no-op; one whose whole delta is a component is\n * merged like any other, through the closure at the end of this function.\n * The merged operations' local component-ref closure rides along recursively;\n * external refs are left untouched and never gated.\n *\n * Every SHAPE question — is there a `paths` mapping, is this path item a\n * mapping, which methods does it hold — is answered from the RESOLVED plain\n * trees, never from the AST node. Asking the AST (`isMap(node)`) answers a\n * different question: an alias node is not a map even when it resolves to one.\n * `paths: *alias` therefore read as \"no paths to merge\" and the whole contract\n * delta was dropped with a successful exit, and an aliased path ITEM fell into\n * the wholesale-replace branch and deleted every living operation on that path\n * that the alias did not restate. Aliases are legal OpenAPI and the natural way\n * to write a delta that repeats a shape; nothing here may treat them as absent.\n * An alias that cannot be resolved at all is a document loam cannot read, and\n * says so — it is never \"nothing to merge\".\n */\nexport function mergeOpenapiPaths(\n livingText: string,\n featureText: string,\n service: string,\n): OpenapiMergeResult {\n // Aliases resolved once, with the document's own anchor context (readTree).\n const { plain: featPlain } = readTree(featureText, openapiRefusal(\"feature\", service));\n const featPathsPlain = plainChild(featPlain, \"paths\");\n // The SHAPE refusal is unchanged and stays first: a `paths` that is a\n // sequence or a scalar is a document loam cannot merge. Absent and null\n // (`paths:` with nothing under it) are not shape faults — they are documents\n // holding no path entries, which is a different sentence and now has a\n // different answer.\n if (featPathsPlain !== undefined && featPathsPlain !== null && !isRecord(featPathsPlain)) {\n throw new OpenapiMergeError(\"feature\", service, \"`paths` is not a mapping\");\n }\n const featPathEntries = isRecord(featPathsPlain) ? Object.entries(featPathsPlain) : [];\n // \"Is there anything to merge?\" is a question about the whole delta, and for\n // years it was asked of `paths` alone — an absence test standing in for the\n // decision. It returned here, before the living document was even parsed, so\n // the component closure (the last statement of this function, and the only\n // thing that writes a component) never ran: a feature whose entire change was\n // a shared schema passed the gate and archived at exit 0 having merged\n // NOTHING. The components answer it too now, over the ONE surface\n // enumeration the gate and the rebase plan already grade the delta with.\n const componentsAreTheDelta = featPathEntries.length === 0;\n if (componentsAreTheDelta && !restatedSurfaces(featPlain).some((s) => s.kind === \"component\")) {\n return noop();\n }\n\n const { doc: living, plain: livingPlain } = readTree(livingText, openapiRefusal(\"living\", service));\n const livingPathsPlain = plainChild(livingPlain, \"paths\");\n if (livingPathsPlain !== undefined && livingPathsPlain !== null && !isRecord(livingPathsPlain)) {\n throw new OpenapiMergeError(\"living\", service, \"`paths` is not a mapping\");\n }\n const modified: string[] = [];\n const modifiedOperationIds = new Map<string, string>();\n const pathItemModified: string[] = [];\n const removed: string[] = [];\n const quoted: string[] = [];\n const baselineStale: string[] = [];\n const pathItemQuoted: string[] = [];\n const pathItemStale: string[] = [];\n // The surface record, read ONCE for the path-item loop and the component\n // closure. Malformed entries read back as absent — the gate refuses them,\n // and a value nobody can evaluate must never be why a merge skips a write.\n const { record } = readBaselineRecord(featPlain);\n // The plain values this merge actually WRITES, labelled for the ref sweep:\n // the closure copies new components reachable from these, and only these —\n // a quoted operation's refs are living's own business.\n const written: Array<{ from: string; value: unknown }> = [];\n for (const [path, featItemPlain] of featPathEntries) {\n const existing = living.getIn([\"paths\", path]);\n const existingPlain = plainChild(livingPathsPlain, path);\n if (existing !== undefined && !isRecord(existingPlain)) {\n // The living contract holds this path as something other than a path\n // item. Replacing it wholesale is the one branch that may delete living\n // operations without naming them, so it is reserved for paths the living\n // contract does not have at all.\n throw new OpenapiMergeError(\"living\", service, `path '${path}' is not a mapping`);\n }\n if (existing !== undefined) {\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n // EVERY branch publishes through withoutFeatureMarkers. This one used to\n // copy every key of the feature path item and guard only the HTTP\n // methods, so a path-level `x-loam-remove` landed in the living contract\n // with `modified: []` and no warning. A key the strip drops is never\n // written; the removal handling below still reads the RAW item, because\n // that is where the markers it acts on live.\n const publish = withoutFeatureMarkers(featItemPlain);\n const publishable = isRecord(publish) ? publish : {};\n for (const [m, afterPlain] of Object.entries(featItemPlain)) {\n const before = living.getIn([\"paths\", path, m]);\n const beforePlain = plainChild(existingPlain, m);\n if (HTTP_METHODS.has(m) && isRemoval(afterPlain)) {\n // Coherence validates the marker and gates absent/mismatched targets.\n // The merge remains defensive under --approve: never delete a\n // different operation merely because it occupies the requested slot.\n if (\n before !== undefined &&\n operationIdOf(beforePlain) !== undefined &&\n operationIdOf(beforePlain) === operationIdOf(afterPlain)\n ) {\n removed.push(opLabel(beforePlain, afterPlain, m, path));\n living.deleteIn([\"paths\", path, m]);\n }\n continue;\n }\n if (HTTP_METHODS.has(m)) {\n const verdict = classifyOperationBaseline(afterPlain, before === undefined ? undefined : beforePlain);\n // A QUOTE is not a merge input. The author wrote this operation down\n // as a restatement, not because they changed it, so\n // the living contract keeps whatever it holds — including a change\n // that landed after this delta was written. This is mechanical, not a\n // judgement, so `--approve` does not turn it back on: overriding a\n // gate is a decision, reverting an operation nobody edited is a bug.\n if (verdict === \"quote\") {\n quoted.push(opLabel(beforePlain, afterPlain, m, path));\n continue;\n }\n if (verdict === \"stale\") baselineStale.push(opLabel(beforePlain, afterPlain, m, path));\n }\n if (!(m in publishable)) {\n // A feature-only marker at path level. It is never published, and\n // never reported as a path-item overwrite either — it is not a key of\n // the contract at all. The archive gate names it to the author.\n continue;\n }\n if (!HTTP_METHODS.has(m)) {\n // The operations' verdict, for the keys beside them, from the root\n // record instead of an in-value pin. A QUOTE is skipped for the\n // operations' reason — the author restated it, they did not edit it\n // — and skipping matters MORE here: a path-level key applies to\n // every operation on the path, including ones this feature never\n // mentions. Stale still writes: reaching the merge means --approve.\n const verdict = classifyBaselineDigests(\n entryFor(record, { kind: \"path-item\", path, key: m, value: afterPlain }),\n valueDigest(afterPlain),\n before === undefined ? undefined : valueDigest(beforePlain),\n );\n if (verdict === \"quote\") {\n pathItemQuoted.push(`'${m}' (${path})`);\n continue;\n }\n if (verdict === \"stale\") pathItemStale.push(`'${m}' (${path})`);\n }\n // The difference check covers EVERY key of the path item; only the\n // LABEL depends on whether the key is an HTTP method.\n if (before !== undefined && !isDeepStrictEqual(beforePlain, afterPlain)) {\n if (HTTP_METHODS.has(m)) {\n const label = opLabel(beforePlain, afterPlain, m, path);\n modified.push(label);\n const id = operationIdOf(beforePlain) ?? operationIdOf(afterPlain);\n if (id !== undefined) modifiedOperationIds.set(label, id);\n } else {\n pathItemModified.push(`'${m}' (${path})`);\n }\n }\n // Every shape question above was answered from the RESOLVED tree, which\n // is exactly what makes an aliased path item read as an ordinary\n // mapping. The WRITE cannot be that forgiving: `setIn` walks the AST,\n // and one shared value backs every use of an anchor, so an operation\n // merged through the alias would land in every other path that\n // references it. There is no way to write into one use of an anchor and\n // not the rest, so this is a refusal rather than a fallback — and it is\n // asked here, at the write, because reading is safe and writing is not.\n if (!isMap(living.getIn([\"paths\", path]))) {\n throw new OpenapiMergeError(\n \"living\",\n service,\n `path '${path}' is written as a YAML alias — one shared value backs every use, so merging into it would rewrite every other path that aliases it. Expand it before archiving.`,\n );\n }\n // Without the strip the living contract would grow a pin to a version\n // of itself, and the NEXT feature's baseline would hash it.\n living.setIn([\"paths\", path, m], publishable[m]);\n written.push({ from: `paths ${path}`, value: publishable[m] });\n }\n // No methods left means the path goes, whatever else survived beside\n // them — the same cleanup stripOpenapiRemovalMarkers does on the\n // feature side, and the reason is spelled once, on the helper.\n dropPathIfNoOperations(living, path);\n continue;\n }\n if (!isRecord(featItemPlain)) {\n throw new OpenapiMergeError(\"feature\", service, `path '${path}' is not a mapping`);\n }\n const clean = withoutFeatureMarkers(featItemPlain);\n if (clean !== undefined) {\n living.setIn([\"paths\", path], clean);\n written.push({ from: `paths ${path}`, value: clean });\n }\n }\n\n const closure = mergeComponentClosure({ living, featPlain, livingPlain, record, written, componentsAreTheDelta });\n const { componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved } = closure;\n\n // Over the living text, so every entry the merge did not touch keeps the\n // author's bytes (`writeTreeOver` says what re-serializing cost).\n const text = writeTreeOver(living, livingText, openapiRefusal(\"living\", service));\n return {\n text, modified, modifiedOperationIds, pathItemModified, removed, quoted, baselineStale,\n pathItemQuoted, pathItemStale, componentsModified, componentsAdded, componentsQuoted, componentsStale, unresolved,\n };\n}\n\n/**\n * The successful \"the feature document has nothing to merge\" answer. Every key\n * of the result is spelled here, empty, rather than left off: the two objects\n * are compared field by field in the tests and read field by field by the plan,\n * and a `noop()` that quietly lacked a key would make \"no delta\" and \"a delta\n * that merged nothing\" structurally different shapes of the same answer.\n */\nfunction noop(): OpenapiMergeResult {\n return {\n text: null, modified: [], modifiedOperationIds: new Map(), pathItemModified: [], removed: [], quoted: [],\n baselineStale: [], pathItemQuoted: [], pathItemStale: [], componentsModified: [], componentsAdded: [],\n componentsQuoted: [], componentsStale: [], unresolved: [],\n };\n}\n\n"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The pin that tells a QUOTE from an EDIT.
|
|
3
3
|
*
|
|
4
|
-
* A feature's openapi.yaml
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A feature's openapi.yaml needs only what it adds, changes or removes, but it
|
|
5
|
+
* may also restate living operations it does not change — for context, or a
|
|
6
|
+
* contract pasted whole — and then much of what it spells is quotation. Without
|
|
7
|
+
* a pin the merge cannot tell the two apart and upserts both,
|
|
7
8
|
* which is how a feature that only meant to touch `cancelOrder` silently
|
|
8
9
|
* reverted another team's landed change to `refundOrder` — no overlap between
|
|
9
10
|
* the two features required, just two deltas over one service.
|
|
@@ -11,19 +12,23 @@
|
|
|
11
12
|
* Separate from `./merge.ts` because this is `loam rebase`'s half: it plans the
|
|
12
13
|
* pins a delta is missing, while the merge only ever reads pins already written.
|
|
13
14
|
*/
|
|
14
|
-
import { isMap
|
|
15
|
+
import { isMap } from "yaml";
|
|
15
16
|
// Every `isRecord` below asks one question of a resolved plain tree: is this a
|
|
16
17
|
// node that can hold OpenAPI keys — an object, not an array?
|
|
17
18
|
import { isRecord } from "../../kernel/records.js";
|
|
18
19
|
import { HTTP_METHODS } from "../doc.js";
|
|
19
20
|
import { classifyBaselineDigests, operationBaselineOf, operationDigest, OPENAPI_BASELINE_KEY, } from "../digest.js";
|
|
20
|
-
import {
|
|
21
|
+
import { openapiRefusal } from "./error.js";
|
|
22
|
+
import { readTree, writeTreeOver } from "../contract-tree.js";
|
|
21
23
|
import { isRemoval } from "../digest.js";
|
|
22
24
|
import { plainChild } from "./markers.js";
|
|
23
25
|
/**
|
|
24
26
|
* Classify one feature operation against its living counterpart, from the raw
|
|
25
|
-
* YAML trees the merge works in
|
|
26
|
-
* the
|
|
27
|
+
* YAML trees the merge works in: what the operation IS, relative to the living
|
|
28
|
+
* contract — the one judgement that tells a QUOTE from an EDIT (the module
|
|
29
|
+
* header says why that judgement exists). The rule itself lives in
|
|
30
|
+
* ../digest.ts, so the gate and the merge cannot drift apart about which
|
|
31
|
+
* operations get written.
|
|
27
32
|
*
|
|
28
33
|
* A malformed pin matches no digest and lands in `stale`, which merges rather
|
|
29
34
|
* than skips. Deliberate: `openapi.baseline-invalid` refuses it at the gate, and
|
|
@@ -51,28 +56,8 @@ export function classifyOperationBaseline(featureOp, livingOp) {
|
|
|
51
56
|
* path the contract does not serve yet.
|
|
52
57
|
*/
|
|
53
58
|
export function pinOpenapiOperations(featureText, livingText, service) {
|
|
54
|
-
const doc =
|
|
55
|
-
|
|
56
|
-
throw new OpenapiMergeError("feature", service, doc.errors[0].message);
|
|
57
|
-
let featPlain;
|
|
58
|
-
let livingPlain;
|
|
59
|
-
try {
|
|
60
|
-
featPlain = doc.toJS() ?? {};
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
throw new OpenapiMergeError("feature", service, errorMessage(error));
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
const parsed = parseDocument(livingText);
|
|
67
|
-
if (parsed.errors.length > 0)
|
|
68
|
-
throw new OpenapiMergeError("living", service, parsed.errors[0].message);
|
|
69
|
-
livingPlain = parsed.toJS() ?? {};
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
if (error instanceof OpenapiMergeError)
|
|
73
|
-
throw error;
|
|
74
|
-
throw new OpenapiMergeError("living", service, errorMessage(error));
|
|
75
|
-
}
|
|
59
|
+
const { doc, plain: featPlain } = readTree(featureText, openapiRefusal("feature", service));
|
|
60
|
+
const { plain: livingPlain } = readTree(livingText, openapiRefusal("living", service));
|
|
76
61
|
const featPaths = plainChild(featPlain, "paths");
|
|
77
62
|
if (!isRecord(featPaths))
|
|
78
63
|
return { text: null, pins: [] };
|
|
@@ -118,11 +103,8 @@ export function pinOpenapiOperations(featureText, livingText, service) {
|
|
|
118
103
|
}
|
|
119
104
|
if (!edited)
|
|
120
105
|
return { text: null, pins };
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
throw new OpenapiMergeError("feature", service, errorMessage(error));
|
|
126
|
-
}
|
|
106
|
+
// Over the author's text, so the pins are the only lines this run changes
|
|
107
|
+
// (`writeTreeOver` says what re-serializing cost).
|
|
108
|
+
return { text: writeTreeOver(doc, featureText, openapiRefusal("feature", service)), pins };
|
|
127
109
|
}
|
|
128
110
|
//# sourceMappingURL=pin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pin.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/pin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pin that tells a QUOTE from an EDIT.\n *\n * A feature's openapi.yaml is a complete document, so most of what it spells is\n * quotation: the author restated the living contract around the slot they were\n * changing. Without a pin the merge cannot tell the two apart and upserts both,\n * which is how a feature that only meant to touch `cancelOrder` silently\n * reverted another team's landed change to `refundOrder` — no overlap between\n * the two features required, just two deltas over one service.\n *\n * Separate from `./merge.ts` because this is `loam rebase`'s half: it plans the\n * pins a delta is missing, while the merge only ever reads pins already written.\n */\nimport { isMap, parseDocument } from \"yaml\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport {\n classifyBaselineDigests,\n operationBaselineOf,\n operationDigest,\n OPENAPI_BASELINE_KEY,\n} from \"../digest.js\";\nimport { errorMessage, OpenapiMergeError } from \"./error.js\";\nimport { isRemoval } from \"../digest.js\";\nimport { plainChild } from \"./markers.js\";\n\n/**\n * What a feature's operation IS, relative to the living contract — the one\n * judgement that tells a QUOTE from an EDIT.\n *\n * A feature's openapi.yaml is a complete document, so most of what it spells is\n * quotation: the author restated the living contract around the slot they were\n * changing. Without a pin the merge cannot tell the two apart and upserts both,\n * which is how a feature that only meant to touch `cancelOrder` silently\n * reverted another team's landed change to `refundOrder` — no overlap between\n * the features required, just two deltas over one service.\n */\nexport type OperationBaselineVerdict = ReturnType<typeof classifyBaselineDigests>;\n\n/**\n * Classify one feature operation against its living counterpart, from the raw\n * YAML trees the merge works in. The rule itself lives in ../digest.ts, so\n * the gate and the merge cannot drift apart about which operations get written.\n *\n * A malformed pin matches no digest and lands in `stale`, which merges rather\n * than skips. Deliberate: `openapi.baseline-invalid` refuses it at the gate, and\n * a value nobody can evaluate must never be the reason a merge quietly drops an\n * operation.\n */\nexport function classifyOperationBaseline(featureOp: unknown, livingOp: unknown): OperationBaselineVerdict {\n return classifyBaselineDigests(\n operationBaselineOf(featureOp),\n operationDigest(featureOp),\n livingOp === undefined ? undefined : operationDigest(livingOp),\n );\n}\n\n/** What happened to one operation's `x-loam-based-on`. */\nexport interface OperationPin {\n path: string;\n method: string;\n /** Empty when the operation declares none — the slot is still the identity. */\n operationId: string;\n status:\n /** It had no pin and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no operation at this slot: this feature is adding it. */\n | \"unresolved\"\n /** Written as a YAML alias — one shared value backs every use, so loam will not stamp through it. */\n | \"unwritable\";\n from: string | null;\n to: string | null;\n}\n\nexport interface OpenapiPinPlan {\n /** The rewritten contract, or null when no pin changes. */\n text: string | null;\n pins: OperationPin[];\n}\n\n/**\n * Pin every operation in a feature contract against the living one — what\n * `loam rebase` writes on this axis.\n *\n * The pin is always `operationDigest` of the LIVING operation, never of the\n * delta's own, and that single rule produces both merge verdicts by itself: an\n * operation the author only QUOTED is byte-equal to living, so its pin equals\n * its own content and `classifyOperationBaseline` calls it a quote; one the\n * author EDITED differs from its pin, so the merge writes it. Nothing has to\n * guess at intent — the document already records it, and the pin makes it\n * legible to the merge.\n *\n * Slot-keyed (path + method), exactly as the merge upserts. An operationId that\n * moved to another slot is a new slot with nothing living behind it, which is\n * `unresolved` and correct: there is no living version of an operation at a\n * path the contract does not serve yet.\n */\nexport function pinOpenapiOperations(featureText: string, livingText: string, service: string): OpenapiPinPlan {\n const doc = parseDocument(featureText);\n if (doc.errors.length > 0) throw new OpenapiMergeError(\"feature\", service, doc.errors[0]!.message);\n let featPlain: unknown;\n let livingPlain: unknown;\n try {\n featPlain = doc.toJS() ?? {};\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n try {\n const parsed = parseDocument(livingText);\n if (parsed.errors.length > 0) throw new OpenapiMergeError(\"living\", service, parsed.errors[0]!.message);\n livingPlain = parsed.toJS() ?? {};\n } catch (error) {\n if (error instanceof OpenapiMergeError) throw error;\n throw new OpenapiMergeError(\"living\", service, errorMessage(error));\n }\n\n const featPaths = plainChild(featPlain, \"paths\");\n if (!isRecord(featPaths)) return { text: null, pins: [] };\n const livingPaths = plainChild(livingPlain, \"paths\");\n\n const pins: OperationPin[] = [];\n let edited = false;\n for (const [path, item] of Object.entries(featPaths)) {\n if (!isRecord(item)) continue;\n for (const [method, op] of Object.entries(item)) {\n if (!HTTP_METHODS.has(method) || !isRecord(op)) continue;\n // A removal marker asserts a slot rather than restating an operation, so\n // it has nothing to be based on; `openapi.remove-target-mismatch` is what\n // guards it against a living slot that moved under it.\n if (isRemoval(op)) continue;\n\n const id = typeof op[\"operationId\"] === \"string\" ? op[\"operationId\"] : \"\";\n const at = { path, method, operationId: id };\n const from = operationBaselineOf(op) ?? null;\n const livingItem = plainChild(livingPaths, path);\n const livingOp = isRecord(livingItem) ? livingItem[method] : undefined;\n if (!isRecord(livingOp)) {\n pins.push({ ...at, status: \"unresolved\", from, to: null });\n continue;\n }\n const digest = operationDigest(livingOp);\n if (from === digest) {\n pins.push({ ...at, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n // One shared value backs every use of an anchor, so `setIn` through an\n // alias would pin every other use of it too — the same reason\n // stripOpenapiRemovalMarkers refuses to edit aliases in place.\n if (!isMap(doc.getIn([\"paths\", path, method]))) {\n pins.push({ ...at, status: \"unwritable\", from, to: null });\n continue;\n }\n pins.push({ ...at, status: from === null ? \"pinned\" : \"repinned\", from, to: digest });\n doc.setIn([\"paths\", path, method, OPENAPI_BASELINE_KEY], digest);\n edited = true;\n }\n }\n\n if (!edited) return { text: null, pins };\n try {\n return { text: doc.toString(), pins };\n } catch (error) {\n throw new OpenapiMergeError(\"feature\", service, errorMessage(error));\n }\n}\n\n\n"]}
|
|
1
|
+
{"version":3,"file":"pin.js","sourceRoot":"","sources":["../../../../src/core/openapi/merge/pin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pin that tells a QUOTE from an EDIT.\n *\n * A feature's openapi.yaml needs only what it adds, changes or removes, but it\n * may also restate living operations it does not change — for context, or a\n * contract pasted whole — and then much of what it spells is quotation. Without\n * a pin the merge cannot tell the two apart and upserts both,\n * which is how a feature that only meant to touch `cancelOrder` silently\n * reverted another team's landed change to `refundOrder` — no overlap between\n * the two features required, just two deltas over one service.\n *\n * Separate from `./merge.ts` because this is `loam rebase`'s half: it plans the\n * pins a delta is missing, while the merge only ever reads pins already written.\n */\nimport { isMap } from \"yaml\";\n// Every `isRecord` below asks one question of a resolved plain tree: is this a\n// node that can hold OpenAPI keys — an object, not an array?\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport {\n classifyBaselineDigests,\n operationBaselineOf,\n operationDigest,\n OPENAPI_BASELINE_KEY,\n} from \"../digest.js\";\nimport { openapiRefusal } from \"./error.js\";\nimport { readTree, writeTreeOver } from \"../contract-tree.js\";\nimport { isRemoval } from \"../digest.js\";\nimport { plainChild } from \"./markers.js\";\n\n/**\n * Classify one feature operation against its living counterpart, from the raw\n * YAML trees the merge works in: what the operation IS, relative to the living\n * contract — the one judgement that tells a QUOTE from an EDIT (the module\n * header says why that judgement exists). The rule itself lives in\n * ../digest.ts, so the gate and the merge cannot drift apart about which\n * operations get written.\n *\n * A malformed pin matches no digest and lands in `stale`, which merges rather\n * than skips. Deliberate: `openapi.baseline-invalid` refuses it at the gate, and\n * a value nobody can evaluate must never be the reason a merge quietly drops an\n * operation.\n */\nexport function classifyOperationBaseline(featureOp: unknown, livingOp: unknown): ReturnType<typeof classifyBaselineDigests> {\n return classifyBaselineDigests(\n operationBaselineOf(featureOp),\n operationDigest(featureOp),\n livingOp === undefined ? undefined : operationDigest(livingOp),\n );\n}\n\n/** What happened to one operation's `x-loam-based-on`. */\nexport interface OperationPin {\n path: string;\n method: string;\n /** Empty when the operation declares none — the slot is still the identity. */\n operationId: string;\n status:\n /** It had no pin and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no operation at this slot: this feature is adding it. */\n | \"unresolved\"\n /** Written as a YAML alias — one shared value backs every use, so loam will not stamp through it. */\n | \"unwritable\";\n from: string | null;\n to: string | null;\n}\n\nexport interface OpenapiPinPlan {\n /** The rewritten contract, or null when no pin changes. */\n text: string | null;\n pins: OperationPin[];\n}\n\n/**\n * Pin every operation in a feature contract against the living one — what\n * `loam rebase` writes on this axis.\n *\n * The pin is always `operationDigest` of the LIVING operation, never of the\n * delta's own, and that single rule produces both merge verdicts by itself: an\n * operation the author only QUOTED is byte-equal to living, so its pin equals\n * its own content and `classifyOperationBaseline` calls it a quote; one the\n * author EDITED differs from its pin, so the merge writes it. Nothing has to\n * guess at intent — the document already records it, and the pin makes it\n * legible to the merge.\n *\n * Slot-keyed (path + method), exactly as the merge upserts. An operationId that\n * moved to another slot is a new slot with nothing living behind it, which is\n * `unresolved` and correct: there is no living version of an operation at a\n * path the contract does not serve yet.\n */\nexport function pinOpenapiOperations(featureText: string, livingText: string, service: string): OpenapiPinPlan {\n const { doc, plain: featPlain } = readTree(featureText, openapiRefusal(\"feature\", service));\n const { plain: livingPlain } = readTree(livingText, openapiRefusal(\"living\", service));\n\n const featPaths = plainChild(featPlain, \"paths\");\n if (!isRecord(featPaths)) return { text: null, pins: [] };\n const livingPaths = plainChild(livingPlain, \"paths\");\n\n const pins: OperationPin[] = [];\n let edited = false;\n for (const [path, item] of Object.entries(featPaths)) {\n if (!isRecord(item)) continue;\n for (const [method, op] of Object.entries(item)) {\n if (!HTTP_METHODS.has(method) || !isRecord(op)) continue;\n // A removal marker asserts a slot rather than restating an operation, so\n // it has nothing to be based on; `openapi.remove-target-mismatch` is what\n // guards it against a living slot that moved under it.\n if (isRemoval(op)) continue;\n\n const id = typeof op[\"operationId\"] === \"string\" ? op[\"operationId\"] : \"\";\n const at = { path, method, operationId: id };\n const from = operationBaselineOf(op) ?? null;\n const livingItem = plainChild(livingPaths, path);\n const livingOp = isRecord(livingItem) ? livingItem[method] : undefined;\n if (!isRecord(livingOp)) {\n pins.push({ ...at, status: \"unresolved\", from, to: null });\n continue;\n }\n const digest = operationDigest(livingOp);\n if (from === digest) {\n pins.push({ ...at, status: \"unchanged\", from: digest, to: digest });\n continue;\n }\n // One shared value backs every use of an anchor, so `setIn` through an\n // alias would pin every other use of it too — the same reason\n // stripOpenapiRemovalMarkers refuses to edit aliases in place.\n if (!isMap(doc.getIn([\"paths\", path, method]))) {\n pins.push({ ...at, status: \"unwritable\", from, to: null });\n continue;\n }\n pins.push({ ...at, status: from === null ? \"pinned\" : \"repinned\", from, to: digest });\n doc.setIn([\"paths\", path, method, OPENAPI_BASELINE_KEY], digest);\n edited = true;\n }\n }\n\n if (!edited) return { text: null, pins };\n // Over the author's text, so the pins are the only lines this run changes\n // (`writeTreeOver` says what re-serializing cost).\n return { text: writeTreeOver(doc, featureText, openapiRefusal(\"feature\", service)), pins };\n}\n\n\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../../../../src/core/openspec/decide/verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,
|
|
1
|
+
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../../../../src/core/openspec/decide/verdict.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Is this migration ready, and what does the answer look like?\n *\n * Readiness is one judgement over everything the scan and the decisions found,\n * and it lives in one function so the six conditions can be read together. Five\n * of them are human decisions and the sixth is the corpus itself; a migration\n * that satisfied five of six used to read as \"nearly ready\", which is not a\n * state `--apply` may act on.\n *\n * The per-axis `readiness` block exists so an agent can say WHICH of the six is\n * open without re-deriving it from the issue list — the same information, but\n * the derivation is here rather than in every reader.\n */\nimport { compareIds } from \"../../repo/entries.js\";\nimport {\n type CapabilityMappingDecision,\n type OpenSpecChangeDecision,\n type OpenSpecMappingIssue,\n} from \"../model/mapping.js\";\nimport {\n OPENSPEC_BASELINES,\n type OpenSpecArtifact,\n type OpenSpecArtifactDecision,\n type OpenSpecCapability,\n type OpenSpecChange,\n type OpenSpecCounts,\n type OpenSpecInventory,\n type OpenSpecRenamedUsage,\n type OpenSpecUnsupportedShape,\n} from \"../model/model.js\";\nimport { sortIssues } from \"../scan/changes.js\";\n\n/** Everything a finished read produced, before it is graded into a verdict. */\nexport interface Findings {\n located: { inputRoot: string; root: string; kind: OpenSpecInventory[\"workspace\"][\"kind\"] };\n inventoryDigest: string;\n config: OpenSpecInventory[\"workspace\"][\"config\"];\n storeMetadataPath: string | null;\n livingCounts: OpenSpecCounts;\n capabilities: OpenSpecCapability[];\n active: OpenSpecChange[];\n archived: OpenSpecChange[];\n renamed: OpenSpecRenamedUsage[];\n blockers: OpenSpecUnsupportedShape[];\n archiveDiagnostics: OpenSpecUnsupportedShape[];\n artifacts: OpenSpecArtifact[];\n mappingDecisions: CapabilityMappingDecision[];\n changeDecisions: OpenSpecChangeDecision[];\n mappingIssues: OpenSpecMappingIssue[];\n artifactDecisions: OpenSpecArtifactDecision[];\n}\n\nexport function inventoryVerdict(found: Findings): OpenSpecInventory {\n const {\n located, inventoryDigest, config, storeMetadataPath, livingCounts, capabilities,\n active, archived, renamed, blockers, archiveDiagnostics, artifacts,\n mappingDecisions, changeDecisions, mappingIssues, artifactDecisions,\n } = found;\n const { inputRoot, root } = located;\n mappingIssues.sort((a, b) => compareIds(a.key, b.key) || compareIds(a.code, b.code));\n\n renamed.sort((a, b) => compareIds(a.path, b.path) || a.line - b.line || compareIds(a.key, b.key));\n sortIssues(blockers);\n sortIssues(archiveDiagnostics);\n const livingIssueCount = blockers.filter((item) => item.scope === \"workspace\" || item.scope === \"living\").length;\n const activeIssueCount = blockers.filter((item) => item.scope === \"active\").length;\n const needsMapping = mappingDecisions.filter((decision) => decision.status === \"needsMapping\");\n const needsChangeMapping = changeDecisions.filter((decision) => decision.status === \"needsFeature\");\n const needsDisposition = artifactDecisions.filter((decision) => decision.status === \"needsDisposition\");\n const unresolvedActiveRenames = renamed.filter(\n (rename) => rename.scope === \"active\" && rename.status === \"needsIdentity\",\n );\n const mechanicallyCompatible = livingIssueCount === 0 && activeIssueCount === 0;\n const ready = mechanicallyCompatible\n && needsMapping.length === 0\n && needsChangeMapping.length === 0\n && unresolvedActiveRenames.length === 0\n && needsDisposition.length === 0\n && mappingIssues.length === 0;\n\n return {\n baselines: OPENSPEC_BASELINES,\n inputRoot,\n root,\n inventoryDigest,\n workspace: {\n kind: located.kind,\n config,\n storeMetadataPath,\n },\n ready,\n mechanicallyCompatible,\n readiness: {\n living: { compatible: livingIssueCount === 0, issueCount: livingIssueCount },\n active: { compatible: activeIssueCount === 0, issueCount: activeIssueCount },\n sourceCurrent: !mappingIssues.some((item) => item.code.startsWith(\"mapping.source-\")),\n mappingsResolved: needsMapping.length === 0 && !mappingIssues.some((item) =>\n item.code === \"mapping.unknown-capability\"\n || item.code === \"mapping.unknown-requirement\"\n || item.code.startsWith(\"mapping.requirement-\")\n || item.code === \"mapping.service-allocation-empty\"),\n changesResolved: needsChangeMapping.length === 0 && !mappingIssues.some((item) =>\n item.code === \"mapping.unknown-change\"\n || item.code === \"mapping.change-title-missing\"\n || item.code.startsWith(\"mapping.feature-id-\")),\n renamesResolved: unresolvedActiveRenames.length === 0 && !mappingIssues.some((item) => item.code.includes(\"rename\") || item.code.includes(\"requirement-id\")),\n dispositionsResolved: needsDisposition.length === 0 && !mappingIssues.some((item) => item.code.includes(\"artifact\")),\n migrationReady: ready,\n },\n living: { ...livingCounts, capabilities },\n changes: {\n active,\n archived,\n counts: { active: active.length, archived: archived.length },\n },\n renamed,\n unsupported: blockers,\n archiveDiagnostics,\n mappingDecisions,\n needsMapping,\n changeDecisions,\n needsChangeMapping,\n mappingIssues,\n artifacts,\n artifactDecisions,\n needsDisposition,\n };\n}\n"]}
|
|
@@ -22,7 +22,7 @@ import { parseRequirements } from "../document/parse.js";
|
|
|
22
22
|
import { emptyMapping } from "./model/mapping.js";
|
|
23
23
|
import {} from "./model/model.js";
|
|
24
24
|
import { locateOpenSpecRoot } from "./scan/workspace.js";
|
|
25
|
-
import { digestInventory, hiddenSubdirs, isSymbolicLink, portable, sourceInventoryPath, subdirs,
|
|
25
|
+
import { digestInventory, hiddenSubdirs, isSymbolicLink, portable, sourceInventoryPath, subdirs, walkTree, } from "./scan/walk.js";
|
|
26
26
|
import { inspectSpecFile, issue } from "./scan/shape.js";
|
|
27
27
|
import { inspectConfig } from "./scan/config.js";
|
|
28
28
|
import { artifactFor, artifactScope, inventoryChanges } from "./scan/changes.js";
|
|
@@ -44,7 +44,7 @@ export async function inventoryOpenSpec(input, options = {}) {
|
|
|
44
44
|
// contract pins — five arrays merged afterwards would be a different report.
|
|
45
45
|
const ingest = { root, mapping, renamed, unsupported: blockers };
|
|
46
46
|
const archiveDiagnostics = [];
|
|
47
|
-
for (const absolute of await
|
|
47
|
+
for (const absolute of (await walkTree(root)).links) {
|
|
48
48
|
const path = portable(root, absolute);
|
|
49
49
|
const scope = artifactScope(path);
|
|
50
50
|
issue(scope === "archive" ? archiveDiagnostics : blockers, scope, {
|
|
@@ -88,7 +88,7 @@ export async function inventoryOpenSpec(input, options = {}) {
|
|
|
88
88
|
const capabilities = [];
|
|
89
89
|
const livingRequirements = new Map();
|
|
90
90
|
const livingCounts = { specFiles: 0, requirements: 0, scenarios: 0 };
|
|
91
|
-
const livingTree = await
|
|
91
|
+
const livingTree = (await walkTree(specsRoot)).files;
|
|
92
92
|
const livingFiles = livingTree.filter((path) => basename(path) === "spec.md");
|
|
93
93
|
// The living twin of openspec.nonstandard-change-spec. Selecting living specs
|
|
94
94
|
// by exact basename is right, but without the mirror a mis-cased or renamed
|
|
@@ -161,7 +161,7 @@ export async function inventoryOpenSpec(input, options = {}) {
|
|
|
161
161
|
scope: "workspace",
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
|
-
const artifactFiles = await
|
|
164
|
+
const artifactFiles = (await walkTree(root)).files;
|
|
165
165
|
if (storeMetadataExists && !storeMetadataSymlink && !artifactFiles.includes(storeMetadata)) {
|
|
166
166
|
artifactFiles.push(storeMetadata);
|
|
167
167
|
}
|
|
@@ -200,7 +200,6 @@ export async function inventoryOpenSpec(input, options = {}) {
|
|
|
200
200
|
const artifactDecisions = artifactDispositions(artifacts, mapping, new Set(active.map((change) => change.id)), mappingIssues);
|
|
201
201
|
return inventoryVerdict({
|
|
202
202
|
located,
|
|
203
|
-
mapping,
|
|
204
203
|
inventoryDigest,
|
|
205
204
|
config,
|
|
206
205
|
storeMetadataPath: storeMetadataExists ? sourceInventoryPath(root, storeMetadata) : null,
|