@ybotok/loam 0.2.0-alpha.6 → 0.2.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1477 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +108 -11
- package/SCHEMA.md +721 -130
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +82 -33
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +64 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +60 -25
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +141 -71
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/report.js +55 -5
- package/dist/commands/subsystem/txn/report.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +121 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +82 -12
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +26 -3
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +35 -32
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +7 -12
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +16 -5
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/consumers.js +43 -122
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/ids.js +33 -7
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +17 -35
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/fleet/views/stale.js +31 -14
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +57 -102
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +31 -17
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +37 -21
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +25 -10
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +25 -22
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +62 -24
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +30 -25
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +79 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +1 -1
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/architecture.js +31 -9
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/documents.js +36 -10
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +14 -21
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +7 -3
- package/dist/core/c4/project/staged.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +15 -16
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +10 -3
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +64 -19
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +34 -29
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +45 -6
- package/dist/core/repo/tree/render/body.js.map +1 -1
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +19 -2
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +102 -33
- package/dist/core/repo/tree/render/views.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +45 -10
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +53 -3
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +28 -9
- package/examples/docs/architecture/landscape.likec4 +21 -7
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +3 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +10 -8
- package/examples/docs/services/payment-service/model.likec4 +6 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { nothingStill, refusalDetails, refusalMessage, stillNamed } from "./refusal.js";
|
|
2
|
+
export async function consumedOperationIssues(request) {
|
|
3
|
+
const { consumers, removingOps, displacedOps } = request;
|
|
4
|
+
const issues = [];
|
|
5
|
+
// --- taking an operation out of the contract while the fleet still calls it ---
|
|
6
|
+
// Exactness and justification never ask what matters to the other
|
|
7
|
+
// ninety-nine repos: is anybody still calling it? The merge deletes the
|
|
8
|
+
// operation while the edge, the requirement and the use-case hop naming it
|
|
9
|
+
// stay, so the next `validate --all` breaks a repository whose author was
|
|
10
|
+
// never in this feature. Asked of the ONE consumer index, read after this
|
|
11
|
+
// feature's merge (core/consumers/ says what it reads and what a feature can
|
|
12
|
+
// retire). `own` counts too: for a marker the REMOVED requirement leaves with
|
|
13
|
+
// the merge, so what remains is a second one, or an internal edge. One index
|
|
14
|
+
// for both shapes and the event axis, so the fleet is read once. A consumer
|
|
15
|
+
// document the index could not read or place refuses too (./refusal.ts):
|
|
16
|
+
// "nobody could look" never let a removal through before the one index.
|
|
17
|
+
const still = async (svc, op) => stillNamed(await consumers.opConsumers(svc, op));
|
|
18
|
+
// The landscape merge only ADDS edges, so an edge is dropped or redrawn in the living map itself.
|
|
19
|
+
const route = `drop or redraw its edge in the architecture/ project, MODIFY or REMOVE the requirement that names it in this feature's specs/<service>/ delta`;
|
|
20
|
+
for (const [svc, retiring] of removingOps) {
|
|
21
|
+
for (const op of retiring) {
|
|
22
|
+
const found = await still(svc, op);
|
|
23
|
+
if (nothingStill(found))
|
|
24
|
+
continue;
|
|
25
|
+
issues.push({
|
|
26
|
+
severity: "error",
|
|
27
|
+
code: "openapi.remove-op-consumed",
|
|
28
|
+
subject: svc,
|
|
29
|
+
details: refusalDetails(found),
|
|
30
|
+
message: refusalMessage(found, {
|
|
31
|
+
subject: svc,
|
|
32
|
+
lost: `this feature retires '${op}'`,
|
|
33
|
+
verb: "consume",
|
|
34
|
+
named: (list) => `${svc}: this feature retires '${op}', but the fleet would still consume it after this merge — ${list}. Retire each consumer in the same change (${route}), or archive with --approve to break it deliberately.`,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// The same loss with no marker to read: another operation defined where a
|
|
40
|
+
// consumed one lives replaces it there, and once the contract the merge
|
|
41
|
+
// would write defines it nowhere it has left the contract exactly as a
|
|
42
|
+
// retirement does. One code, because the breach and its override are the
|
|
43
|
+
// removal's — only `api.op-unconsumed` (advisory) said anything before, and
|
|
44
|
+
// the archive passed without `--approve`. With no marker there is no REMOVED
|
|
45
|
+
// requirement either, so the provider's own requirement naming the old id
|
|
46
|
+
// stays unless this feature restates it — `own` is what catches it.
|
|
47
|
+
for (const [svc, displaced] of displacedOps) {
|
|
48
|
+
for (const { id: op, at } of displaced) {
|
|
49
|
+
const found = await still(svc, op);
|
|
50
|
+
if (nothingStill(found))
|
|
51
|
+
continue;
|
|
52
|
+
const by = at.map(({ address, by: id }) => `${id === undefined ? "an operation with no operationId" : `'${id}'`} at ${address}`).join(" and ");
|
|
53
|
+
const lost = `this feature's openapi.yaml defines ${by}, where '${op}' lives now — the merge replaces it there and would leave '${op}' defined nowhere`;
|
|
54
|
+
issues.push({
|
|
55
|
+
severity: "error",
|
|
56
|
+
code: "openapi.remove-op-consumed",
|
|
57
|
+
subject: svc,
|
|
58
|
+
details: refusalDetails(found),
|
|
59
|
+
message: refusalMessage(found, {
|
|
60
|
+
subject: svc,
|
|
61
|
+
lost,
|
|
62
|
+
verb: "consume",
|
|
63
|
+
named: (list) => `${svc}: ${lost}, but it is still consumed — ${list}. Define '${op}' at another address in this delta if it is to stay. To take it out on purpose, update every consumer in the same change — ${route} (${svc}'s own restated to name the new operation, for a rename; a REMOVED one owes no x-loam-remove marker, since the overwrite already takes '${op}' out) — or archive with --approve to break it deliberately.`,
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return issues;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../../../src/core/coherence/consumed/operations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The contract axis's removal gate, `openapi.remove-op-consumed`: does the\n * fleet still consume an operation this feature takes out of the contract —\n * by a removal marker, or by defining another operation where it lives?\n *\n * Moved out of `../coherence.ts`, which ran at its 400-line limit, along the\n * seam the gate already had there: everything below asks the consumer index\n * (core/consumers/) one question per operation the merge loses and phrases\n * the refusal, and nothing else in featureCoherence reads its answer. The\n * index itself is built there and handed in, because the event axis's twin\n * (`asyncapi.remove-message-consumed`, ../events/events.ts) asks the same one.\n */\nimport type { Issue } from \"../../vocabulary/issue.js\";\nimport type { ConsumerIndex } from \"../../consumers/consumers.js\";\nimport type { Declared } from \"../declared.js\";\nimport { nothingStill, refusalDetails, refusalMessage, stillNamed, type StillNamed } from \"./refusal.js\";\n\n/** What the gate is asked over: the shared index, and the two ways this feature's merge loses an operation. */\nexport interface ConsumedOperationsRequest {\n /** The consumer index, read after this feature's merge — shared with the event axis so the fleet is read once. */\n consumers: ConsumerIndex;\n removingOps: Declared[\"removingOps\"];\n displacedOps: Declared[\"displacedOps\"];\n}\n\nexport async function consumedOperationIssues(request: ConsumedOperationsRequest): Promise<Issue[]> {\n const { consumers, removingOps, displacedOps } = request;\n const issues: Issue[] = [];\n // --- taking an operation out of the contract while the fleet still calls it ---\n // Exactness and justification never ask what matters to the other\n // ninety-nine repos: is anybody still calling it? The merge deletes the\n // operation while the edge, the requirement and the use-case hop naming it\n // stay, so the next `validate --all` breaks a repository whose author was\n // never in this feature. Asked of the ONE consumer index, read after this\n // feature's merge (core/consumers/ says what it reads and what a feature can\n // retire). `own` counts too: for a marker the REMOVED requirement leaves with\n // the merge, so what remains is a second one, or an internal edge. One index\n // for both shapes and the event axis, so the fleet is read once. A consumer\n // document the index could not read or place refuses too (./refusal.ts):\n // \"nobody could look\" never let a removal through before the one index.\n const still = async (svc: string, op: string): Promise<StillNamed> => stillNamed(await consumers.opConsumers(svc, op));\n // The landscape merge only ADDS edges, so an edge is dropped or redrawn in the living map itself.\n const route = `drop or redraw its edge in the architecture/ project, MODIFY or REMOVE the requirement that names it in this feature's specs/<service>/ delta`;\n for (const [svc, retiring] of removingOps) {\n for (const op of retiring) {\n const found = await still(svc, op);\n if (nothingStill(found)) continue;\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-op-consumed\",\n subject: svc,\n details: refusalDetails(found),\n message: refusalMessage(found, {\n subject: svc,\n lost: `this feature retires '${op}'`,\n verb: \"consume\",\n named: (list) => `${svc}: this feature retires '${op}', but the fleet would still consume it after this merge — ${list}. Retire each consumer in the same change (${route}), or archive with --approve to break it deliberately.`,\n }),\n });\n }\n }\n\n // The same loss with no marker to read: another operation defined where a\n // consumed one lives replaces it there, and once the contract the merge\n // would write defines it nowhere it has left the contract exactly as a\n // retirement does. One code, because the breach and its override are the\n // removal's — only `api.op-unconsumed` (advisory) said anything before, and\n // the archive passed without `--approve`. With no marker there is no REMOVED\n // requirement either, so the provider's own requirement naming the old id\n // stays unless this feature restates it — `own` is what catches it.\n for (const [svc, displaced] of displacedOps) {\n for (const { id: op, at } of displaced) {\n const found = await still(svc, op);\n if (nothingStill(found)) continue;\n const by = at.map(({ address, by: id }) => `${id === undefined ? \"an operation with no operationId\" : `'${id}'`} at ${address}`).join(\" and \");\n const lost = `this feature's openapi.yaml defines ${by}, where '${op}' lives now — the merge replaces it there and would leave '${op}' defined nowhere`;\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-op-consumed\",\n subject: svc,\n details: refusalDetails(found),\n message: refusalMessage(found, {\n subject: svc,\n lost,\n verb: \"consume\",\n named: (list) => `${svc}: ${lost}, but it is still consumed — ${list}. Define '${op}' at another address in this delta if it is to stay. To take it out on purpose, update every consumer in the same change — ${route} (${svc}'s own restated to name the new operation, for a rename; a REMOVED one owes no x-loam-remove marker, since the overwrite already takes '${op}' out) — or archive with --approve to break it deliberately.`,\n }),\n });\n }\n }\n return issues;\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { documentDoubt } from "../../consumers/scan.js";
|
|
2
|
+
/**
|
|
3
|
+
* The gate's reading of one answer: `victims` and `own` as evidence, and of the
|
|
4
|
+
* suspensions only the document doubts. A caller drawn with no op and a
|
|
5
|
+
* contested hop stay out — `documentDoubt` (core/consumers/scan.ts) says why
|
|
6
|
+
* they are a warning's worth in `loam diff` and nothing here.
|
|
7
|
+
*/
|
|
8
|
+
export function stillNamed(found) {
|
|
9
|
+
return {
|
|
10
|
+
named: [...found.victims, ...found.own],
|
|
11
|
+
unchecked: [...found.suspended, ...found.ownSuspended].filter(documentDoubt).map((s) => s.text),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Nothing named and nothing unchecked: the removal is the gate's to let through. */
|
|
15
|
+
export function nothingStill(still) {
|
|
16
|
+
return still.named.length === 0 && still.unchecked.length === 0;
|
|
17
|
+
}
|
|
18
|
+
/** The one remedy every unchecked document shares, after "make". */
|
|
19
|
+
const READABLE = "each document listed readable (re-save it as UTF-8, or leave one directory per service id) so the gate can answer";
|
|
20
|
+
/**
|
|
21
|
+
* The message for one refusal. With named consumers it is the gate's own
|
|
22
|
+
* sentence, plus one more when documents went unchecked too; with only
|
|
23
|
+
* unchecked documents it never claims a consumer — "nobody could look" is not
|
|
24
|
+
* "somebody still calls it", any more than it is "nobody does".
|
|
25
|
+
*/
|
|
26
|
+
export function refusalMessage(still, wording) {
|
|
27
|
+
const unchecked = still.unchecked.join("; ");
|
|
28
|
+
if (still.named.length === 0) {
|
|
29
|
+
return `${wording.subject}: ${wording.lost}, but whether the fleet would still ${wording.verb} it after this merge could not be checked — ${unchecked}. Make ${READABLE}, or archive with --approve to break it deliberately.`;
|
|
30
|
+
}
|
|
31
|
+
const named = wording.named(still.named.join("; "));
|
|
32
|
+
return still.unchecked.length === 0 ? named : `${named} Whether anything else would still ${wording.verb} it could not be checked either — ${unchecked}: make ${READABLE}.`;
|
|
33
|
+
}
|
|
34
|
+
/** What a refusal's `details[]` lists: the named consumers, then the documents nobody could check. */
|
|
35
|
+
export function refusalDetails(still) {
|
|
36
|
+
return [...still.named, ...still.unchecked];
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refusal.js","sourceRoot":"","sources":["../../../../src/core/coherence/consumed/refusal.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAUxD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAgB;IACzC,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC;QACvC,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KAChG,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC;AAClE,CAAC;AAaD,oEAAoE;AACpE,MAAM,QAAQ,GAAG,mHAAmH,CAAC;AAErI;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAiB,EAAE,OAAuB;IACvE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,GAAG,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,uCAAuC,OAAO,CAAC,IAAI,+CAA+C,SAAS,UAAU,QAAQ,uDAAuD,CAAC;IACjO,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,sCAAsC,OAAO,CAAC,IAAI,qCAAqC,SAAS,UAAU,QAAQ,GAAG,CAAC;AAC9K,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,cAAc,CAAC,KAAiB;IAC9C,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;AAC9C,CAAC","sourcesContent":["/**\n * What a removal refusal counts of one consumer-index answer, and how it says\n * so — shared by the operation gate (./operations.ts) and its event twin\n * (../events/events.ts), so a consumer nobody could read refuses the same way,\n * in the same sentence, on both axes.\n *\n * Two lists, because they are two different claims. `named` is positive\n * evidence — the merge would strand these — and its sentence is each gate's\n * own, with the remedy that fits the shape. `unchecked` is a document nobody\n * could read or place, so the refusal must not say the fleet still consumes\n * anything: it says the gate could not tell, names the documents, and gives the\n * one remedy they share. Both land in `details[]`, the named ones first — the\n * order `loam diff` lists victims and doubts in.\n */\nimport type { Consumers } from \"../../consumers/consumers.js\";\nimport { documentDoubt } from \"../../consumers/scan.js\";\n\n/** What the gate counts of one index answer. */\nexport interface StillNamed {\n /** The consumers the merge would strand, then the provider's own documents still naming it. */\n named: string[];\n /** The documents — a consumer's, or the provider's own — the scan could not read or place (`documentDoubt`). */\n unchecked: string[];\n}\n\n/**\n * The gate's reading of one answer: `victims` and `own` as evidence, and of the\n * suspensions only the document doubts. A caller drawn with no op and a\n * contested hop stay out — `documentDoubt` (core/consumers/scan.ts) says why\n * they are a warning's worth in `loam diff` and nothing here.\n */\nexport function stillNamed(found: Consumers): StillNamed {\n return {\n named: [...found.victims, ...found.own],\n unchecked: [...found.suspended, ...found.ownSuspended].filter(documentDoubt).map((s) => s.text),\n };\n}\n\n/** Nothing named and nothing unchecked: the removal is the gate's to let through. */\nexport function nothingStill(still: StillNamed): boolean {\n return still.named.length === 0 && still.unchecked.length === 0;\n}\n\n/** One refusal's sentence, as the gate that raises it phrases it. */\nexport interface RefusalWording {\n subject: string;\n /** How this feature's merge takes it out — the opening clause, e.g. \"this feature retires 'capturePayment'\". */\n lost: string;\n /** What a consumer does with it: \"consume\" for an operation, \"name\" for a message. */\n verb: string;\n /** The gate's own sentence over its named consumers, given them joined. */\n named: (list: string) => string;\n}\n\n/** The one remedy every unchecked document shares, after \"make\". */\nconst READABLE = \"each document listed readable (re-save it as UTF-8, or leave one directory per service id) so the gate can answer\";\n\n/**\n * The message for one refusal. With named consumers it is the gate's own\n * sentence, plus one more when documents went unchecked too; with only\n * unchecked documents it never claims a consumer — \"nobody could look\" is not\n * \"somebody still calls it\", any more than it is \"nobody does\".\n */\nexport function refusalMessage(still: StillNamed, wording: RefusalWording): string {\n const unchecked = still.unchecked.join(\"; \");\n if (still.named.length === 0) {\n return `${wording.subject}: ${wording.lost}, but whether the fleet would still ${wording.verb} it after this merge could not be checked — ${unchecked}. Make ${READABLE}, or archive with --approve to break it deliberately.`;\n }\n const named = wording.named(still.named.join(\"; \"));\n return still.unchecked.length === 0 ? named : `${named} Whether anything else would still ${wording.verb} it could not be checked either — ${unchecked}: make ${READABLE}.`;\n}\n\n/** What a refusal's `details[]` lists: the named consumers, then the documents nobody could check. */\nexport function refusalDetails(still: StillNamed): string[] {\n return [...still.named, ...still.unchecked];\n}\n"]}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What a feature's own delta DECLARES, per service, indexed once.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Nine indexes over the same four files (the delta spec.md, arch.spec.md,
|
|
5
5
|
* openapi.yaml, and the living contract they are graded against), built in one
|
|
6
6
|
* walk because every check downstream joins on the service id and would
|
|
7
7
|
* otherwise re-read the delta per question. The distinctions here are the ones
|
|
8
8
|
* the checks cannot make for themselves: an operation a REMOVED requirement
|
|
9
9
|
* governs is being retired, so it neither claims the contract nor governs
|
|
10
10
|
* anything after the merge; a removal marker that names an id also defined
|
|
11
|
-
* elsewhere in the delta is a RELOCATION, not a retirement;
|
|
11
|
+
* elsewhere in the delta is a RELOCATION, not a retirement; a living operation
|
|
12
|
+
* another one replaces at its address, with no marker naming it, leaves the
|
|
13
|
+
* contract all the same; and a marker with
|
|
12
14
|
* no `operationId` at all is a shape only this walk can see. The capability
|
|
13
15
|
* gate rides the same loop: both delta spec documents are graded against the
|
|
14
16
|
* fleet vocabulary here, because coherence.ts and this package's file count
|
|
15
17
|
* are both at their limits and the walk already holds the parsed documents.
|
|
16
18
|
*/
|
|
17
19
|
import { existsSync } from "node:fs";
|
|
18
|
-
import {
|
|
20
|
+
import { readOr } from "../openapi/contract-text.js";
|
|
19
21
|
import { readFile } from "node:fs/promises";
|
|
20
22
|
import {} from "../kernel/ids/service.js";
|
|
21
23
|
import { featureSpecPaths } from "../repo/paths.js";
|
|
@@ -29,6 +31,8 @@ import { realizesUnknownIssues } from "../capabilities/realizes/findings.js";
|
|
|
29
31
|
import { capabilityUnknownIssues } from "../capabilities/findings.js";
|
|
30
32
|
import { openapiBaselineIssues } from "../openapi/baseline/gate.js";
|
|
31
33
|
import { readOpenapi } from "../openapi/doc.js";
|
|
34
|
+
import { operationRetirements } from "./retired/retirement.js";
|
|
35
|
+
import { operationArrivals } from "./retired/revival/arrivals.js";
|
|
32
36
|
export async function declaredByService(scope, svcNames, issues, context) {
|
|
33
37
|
const { docsDir, featureDir, featureId } = scope;
|
|
34
38
|
const reqOps = new Map();
|
|
@@ -41,6 +45,10 @@ export async function declaredByService(scope, svcNames, issues, context) {
|
|
|
41
45
|
const anonymousMarkers = new Map();
|
|
42
46
|
const featureApiOps = new Set();
|
|
43
47
|
const unreadableApis = new Set();
|
|
48
|
+
const revivalCandidates = new Map();
|
|
49
|
+
const displacedOps = new Map();
|
|
50
|
+
/** Per service read here: the operationIds its living contract holds, or null when it does not read. */
|
|
51
|
+
const livingIds = new Map();
|
|
44
52
|
// The capability gate's vocabulary, once per call. Absent or invalid stays
|
|
45
53
|
// SILENT here — capability.invalid is validate --all's one finding about a
|
|
46
54
|
// broken file, and an absent file means the fleet never opted in — so with
|
|
@@ -108,8 +116,12 @@ export async function declaredByService(scope, svcNames, issues, context) {
|
|
|
108
116
|
issues.push(...capabilityUnknownIssues(archReqs, archTarget, capabilityVocab));
|
|
109
117
|
issues.push(...realizesUnknownIssues(archReqs, archTarget, await capabilityReqs()));
|
|
110
118
|
}
|
|
111
|
-
// Only operations
|
|
112
|
-
//
|
|
119
|
+
// Only operations the living contract does not hold count as feature-added:
|
|
120
|
+
// a delta may restate living operations it does not change (a quote), and
|
|
121
|
+
// a quote adds nothing. "Not in living" is not quite "new", though — an
|
|
122
|
+
// operation an archived feature RETIRED is absent too, and adding it back
|
|
123
|
+
// is `openapi.op-revived`'s question (./retired/revival/revived.ts), asked of the
|
|
124
|
+
// `revivalCandidates` collected below.
|
|
113
125
|
const featDoc = await readOpenapi(paths.openapi, context);
|
|
114
126
|
// A feature contract that EXISTS but does not read is a broken document,
|
|
115
127
|
// not an empty one — the living-side `openapi.invalid` discipline
|
|
@@ -131,15 +143,10 @@ export async function declaredByService(scope, svcNames, issues, context) {
|
|
|
131
143
|
}
|
|
132
144
|
const featOps = featDoc.ops;
|
|
133
145
|
const removals = featOps.filter((op) => op.remove);
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
// Conflating them made moving an endpoint fail three checks at once for a
|
|
139
|
-
// change that removes nothing.
|
|
140
|
-
const redefined = new Set(featOps.filter((op) => !op.remove).map((op) => op.id));
|
|
141
|
-
const markerIds = new Set(removals.map((op) => op.id));
|
|
142
|
-
const netRemoved = new Set([...markerIds].filter((id) => !redefined.has(id)));
|
|
146
|
+
// The raw marker set and the NET one (markers minus relocations) —
|
|
147
|
+
// ./retired/retirement.ts owns the rule and says why the two differ; the
|
|
148
|
+
// retired index reads every archived feature through the same function.
|
|
149
|
+
const { markerIds, net: netRemoved } = operationRetirements(featOps);
|
|
143
150
|
removingOps.set(svc, netRemoved);
|
|
144
151
|
allMarkerIds.set(svc, markerIds);
|
|
145
152
|
anonymousMarkers.set(svc, featDoc.anonymousRemovals.length > 0);
|
|
@@ -155,8 +162,15 @@ export async function declaredByService(scope, svcNames, issues, context) {
|
|
|
155
162
|
message: `${svc}: ${marker.method} ${marker.path} carries x-loam-remove: true but declares no operationId — loam cannot tell which operation it retires; name the operation the living contract has at that slot`,
|
|
156
163
|
});
|
|
157
164
|
}
|
|
158
|
-
const
|
|
165
|
+
const livingOpenapiPath = (await locateServicePaths(docsDir, svc, context)).openapi;
|
|
166
|
+
const livingDoc = await readOpenapi(livingOpenapiPath, context);
|
|
159
167
|
const livingOps = livingDoc.ops;
|
|
168
|
+
livingIds.set(svc, livingDoc.unreadable ? null : new Set(livingOps.filter((op) => !op.remove).map((op) => op.id)));
|
|
169
|
+
const sides = { featPath: paths.openapi, featDoc, livingPath: livingOpenapiPath, livingDoc, service: svc };
|
|
170
|
+
const moved = await operationArrivals(sides, context);
|
|
171
|
+
revivalCandidates.set(svc, moved.operations);
|
|
172
|
+
if (moved.displaced.length > 0)
|
|
173
|
+
displacedOps.set(svc, moved.displaced);
|
|
160
174
|
for (const id of livingDoc.duplicateIds) {
|
|
161
175
|
const slots = livingOps.filter((op) => op.id === id).map((op) => `${op.method} ${op.path}`);
|
|
162
176
|
issues.push({
|
|
@@ -172,27 +186,14 @@ export async function declaredByService(scope, svcNames, issues, context) {
|
|
|
172
186
|
// read when there is one, mirroring the readRequirements pattern above;
|
|
173
187
|
// the gate itself never touches the filesystem.
|
|
174
188
|
if (existsSync(paths.openapi)) {
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// flag the document unreadable. Uncaught, one service's UTF-16 living
|
|
179
|
-
// contract took down the whole feature target under validate's
|
|
180
|
-
// context while archive's no-context path sailed past — the exact
|
|
189
|
+
// `readOr` (../openapi/contract-text.ts, which says why): `context.readText`
|
|
190
|
+
// throws on non-UTF-8 bytes where the no-context `readFile` lets
|
|
191
|
+
// `readOpenapi` flag the document unreadable — the exact
|
|
181
192
|
// context/no-context divergence doc.ts documents as paid for once.
|
|
182
193
|
// Either text failing to decode gets the same answer the gate gives
|
|
183
194
|
// an unreadable parse: that side's surface checks are skipped.
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
return context === undefined ? await readFile(p, "utf8") : await context.readText(p);
|
|
187
|
-
}
|
|
188
|
-
catch (err) {
|
|
189
|
-
if (err instanceof NotUtf8DocumentError)
|
|
190
|
-
return undefined;
|
|
191
|
-
throw err;
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
const featureText = await readOr(paths.openapi);
|
|
195
|
-
const livingText = !existsSync(livingOpenapiPath) ? undefined : await readOr(livingOpenapiPath);
|
|
195
|
+
const featureText = await readOr(paths.openapi, context);
|
|
196
|
+
const livingText = !existsSync(livingOpenapiPath) ? undefined : await readOr(livingOpenapiPath, context);
|
|
196
197
|
if (featureText !== undefined) {
|
|
197
198
|
issues.push(...openapiBaselineIssues({ featDoc, livingDoc, featureText, livingText, service: svc, featureId }));
|
|
198
199
|
}
|
|
@@ -246,9 +247,31 @@ export async function declaredByService(scope, svcNames, issues, context) {
|
|
|
246
247
|
if (unreadableApis.has(svc))
|
|
247
248
|
continue;
|
|
248
249
|
const marked = allMarkerIds.get(svc) ?? new Set();
|
|
250
|
+
const replaced = new Set((displacedOps.get(svc) ?? []).map((d) => d.id));
|
|
251
|
+
const living = livingIds.get(svc);
|
|
249
252
|
for (const op of required) {
|
|
250
253
|
if (marked.has(op))
|
|
251
254
|
continue;
|
|
255
|
+
// A marker is owed only while there is a living operation for it to
|
|
256
|
+
// retire — the event axis's rule (events/declared.ts) on the contract
|
|
257
|
+
// axis. An operation this delta already REPLACES at its address leaves
|
|
258
|
+
// the contract without one, and the marker could not even be written:
|
|
259
|
+
// the slot holds the replacement. So the route the overwrite's
|
|
260
|
+
// `openapi.remove-op-consumed` names — REMOVE the requirement governing
|
|
261
|
+
// the old id — ended in this error, followable only by deleting the
|
|
262
|
+
// requirement's Operations: line, which nothing said (review
|
|
263
|
+
// 2026-10-12, finding 4). And an id the service's living contract does
|
|
264
|
+
// not define is not this service's to mark at all: a REMOVED consumer
|
|
265
|
+
// requirement naming ANOTHER service's operation was told to retire it
|
|
266
|
+
// from its own contract. Both exemptions are facts about a contract
|
|
267
|
+
// loam READ: a living openapi.yaml that does not parse proves neither,
|
|
268
|
+
// so the marker stays owed — the closed direction, and the answer this
|
|
269
|
+
// check gave before either exemption existed. Skipping it there let a
|
|
270
|
+
// REMOVED requirement with no marker pass at exit 0 (the contract
|
|
271
|
+
// review, finding 3); `openapi.invalid` is still validate's finding
|
|
272
|
+
// about the file itself.
|
|
273
|
+
if (replaced.has(op) || (living !== undefined && living !== null && !living.has(op)))
|
|
274
|
+
continue;
|
|
252
275
|
// Distinguish "no marker at all" from "a marker is there but loam cannot
|
|
253
276
|
// read which operation it names" — the first asks the author to write the
|
|
254
277
|
// marker, the second to write the operationId, and telling somebody who
|
|
@@ -264,6 +287,9 @@ export async function declaredByService(scope, svcNames, issues, context) {
|
|
|
264
287
|
});
|
|
265
288
|
}
|
|
266
289
|
}
|
|
267
|
-
return {
|
|
290
|
+
return {
|
|
291
|
+
reqOps, removedReqOps, removingOps, allMarkerIds, anonymousMarkers, featureApiOps, unreadableApis, revivalCandidates,
|
|
292
|
+
displacedOps,
|
|
293
|
+
};
|
|
268
294
|
}
|
|
269
295
|
//# sourceMappingURL=declared.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"declared.js","sourceRoot":"","sources":["../../../src/core/coherence/declared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a feature's own delta DECLARES, per service, indexed once.\n *\n * Seven indexes over the same four files (the delta spec.md, arch.spec.md,\n * openapi.yaml, and the living contract they are graded against), built in one\n * walk because every check downstream joins on the service id and would\n * otherwise re-read the delta per question. The distinctions here are the ones\n * the checks cannot make for themselves: an operation a REMOVED requirement\n * governs is being retired, so it neither claims the contract nor governs\n * anything after the merge; a removal marker that names an id also defined\n * elsewhere in the delta is a RELOCATION, not a retirement; and a marker with\n * no `operationId` at all is a shape only this walk can see. The capability\n * gate rides the same loop: both delta spec documents are graded against the\n * fleet vocabulary here, because coherence.ts and this package's file count\n * are both at their limits and the walk already holds the parsed documents.\n */\nimport { existsSync } from \"node:fs\";\nimport { NotUtf8DocumentError } from \"../kernel/document-bytes.js\";\nimport { readFile } from \"node:fs/promises\";\nimport { type PathableService } from \"../kernel/ids/service.js\";\nimport type { Issue } from \"../vocabulary/issue.js\";\nimport { featureSpecPaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport { readCapabilityVocabulary } from \"../capabilities/capabilities.js\";\nimport { capabilityRequirementIndex } from \"../capabilities/findings.js\";\nimport { withFeatureCapabilities, withFeatureRequirements } from \"../capabilities/delta/overlay.js\";\nimport { featureCapabilityDeltas } from \"../capabilities/delta/tree.js\";\nimport { realizesUnknownIssues } from \"../capabilities/realizes/findings.js\";\nimport type { CapabilityRequirementIndex } from \"../capabilities/realizes/join.js\";\nimport { capabilityUnknownIssues } from \"../capabilities/findings.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport { openapiBaselineIssues } from \"../openapi/baseline/gate.js\";\nimport { readOpenapi } from \"../openapi/doc.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\n\n/** Everything one feature's delta says about its own services. */\nexport interface Declared {\n /** Per service: the operations its non-REMOVED requirements govern. */\n reqOps: Map<PathableService, string[]>;\n /** Per service: the operations its REMOVED requirements governed. */\n removedReqOps: Map<string, Set<string>>;\n /** Per service: operations this feature genuinely retires (markers minus relocations). */\n removingOps: Map<string, Set<string>>;\n /** Per service: every id a removal marker names, relocations included. */\n allMarkerIds: Map<string, Set<string>>;\n /** Per service: does the feature contract carry a marker with no operationId? */\n anonymousMarkers: Map<string, boolean>;\n /** The operationIds genuinely NEW to their service across the whole feature. */\n featureApiOps: Set<string>;\n /**\n * Services whose FEATURE openapi.yaml exists but does not parse. Every check\n * that joins on the feature contract must suspend itself for these — an\n * unreadable document yields the same empty op set as an absent one, and\n * grading against that emptiness produced false errors pointing everywhere\n * but at the broken file.\n */\n unreadableApis: Set<string>;\n}\n\n/** The three facts this walk needs about the feature it is reading. */\nexport interface DeltaScope {\n docsDir: DocsDir;\n featureDir: FeatureDir;\n featureId: string;\n}\n\nexport async function declaredByService(\n scope: DeltaScope,\n svcNames: PathableService[],\n issues: Issue[],\n context?: FleetContext,\n): Promise<Declared> {\n const { docsDir, featureDir, featureId } = scope;\n const reqOps = new Map<PathableService, string[]>();\n const removedReqOps = new Map<string, Set<string>>();\n /** Per service: operations this feature genuinely retires (markers minus relocations). */\n const removingOps = new Map<string, Set<string>>();\n /** Per service: every id a removal marker names, relocations included. */\n const allMarkerIds = new Map<string, Set<string>>();\n /** Per service: does the feature contract carry a marker with no operationId? */\n const anonymousMarkers = new Map<string, boolean>();\n const featureApiOps = new Set<string>();\n const unreadableApis = new Set<string>();\n // The capability gate's vocabulary, once per call. Absent or invalid stays\n // SILENT here — capability.invalid is validate --all's one finding about a\n // broken file, and an absent file means the fleet never opted in — so with\n // either, nothing new gates archive (the roadmap's own trade; the\n // /loam-check row tells agents to fix the YAML first). `grading` is that\n // silence hoisted into a guard, so a silent axis also skips the reads that\n // exist only to feed it.\n //\n // The vocabulary is the one this feature's MERGE would leave behind, not\n // the one on disk: a feature that adds a capability requirement in its own\n // capability delta and `Realizes:` it from its own service delta is the\n // flow the whole axis exists for, and graded against the living tree alone\n // it is refused with `capability.realizes-unknown` — an error, gating\n // archive, for a target the same archive creates. `capabilities/delta/\n // overlay.ts` states the whole judgement. One `existsSync` for a feature\n // that carries no capability delta, which is every feature in a fleet that\n // has not adopted the axis.\n const capabilityDeltas =\n context === undefined ? await featureCapabilityDeltas(featureDir) : await context.featureCapabilityDeltas(featureDir);\n const capabilityVocab = withFeatureCapabilities(\n context === undefined ? await readCapabilityVocabulary(docsDir) : await context.capabilities(docsDir),\n capabilityDeltas.docs.map((d) => d.id),\n );\n const grading = capabilityVocab.present && capabilityVocab.invalid === undefined;\n // The `Realizes:` index, built at most ONCE per feature and only if some\n // delta document actually reaches the grading branch. Lazy rather than\n // eager because it reads every capability document in the fleet, and a\n // feature whose deltas carry no requirement file at all must not pay for\n // that — the same \"a silent axis skips the reads that feed it\" rule\n // `grading` above states, one level deeper.\n let pendingIndex: Promise<CapabilityRequirementIndex> | null = null;\n const capabilityReqs = (): Promise<CapabilityRequirementIndex> => {\n const read = context === undefined\n ? async (p: string): Promise<Requirement[]> => parseRequirements(await readFile(p, \"utf8\"))\n : (p: string): Promise<Requirement[]> => context.readRequirements(p);\n // The living index first, then widened by what this feature's capability\n // deltas ADD or MODIFY. Both halves are inside the lazy thunk, so a\n // feature whose deltas carry no requirement file at all pays for neither.\n pendingIndex ??= capabilityRequirementIndex(capabilityVocab, read).then(async (index) =>\n withFeatureRequirements(\n index,\n await Promise.all(capabilityDeltas.docs.map(async (d) => ({ id: d.id, reqs: await read(d.spec) }))),\n ),\n );\n return pendingIndex;\n };\n for (const svc of svcNames) {\n const paths = featureSpecPaths(featureDir, svc);\n if (existsSync(paths.spec)) {\n const reqs = context === undefined\n ? parseRequirements(await readFile(paths.spec, \"utf8\"))\n : await context.readRequirements(paths.spec);\n // REMOVED requirements are being retired along with their operations — their\n // ops neither claim the contract (E1) nor govern anything after the merge.\n reqOps.set(svc, reqs.filter((r) => r.kind !== \"REMOVED\").flatMap((r) => r.operations));\n removedReqOps.set(\n svc,\n new Set(reqs.filter((r) => r.kind === \"REMOVED\").flatMap((r) => r.operations)),\n );\n if (grading) {\n const target = { where: `${svc}: spec.md`, subject: svc };\n issues.push(...capabilityUnknownIssues(reqs, target, capabilityVocab));\n issues.push(...realizesUnknownIssues(reqs, target, await capabilityReqs()));\n }\n }\n // The delta arch.spec.md carries the same grammar and merges the same\n // way, so an undeclared capability in it gates identically. Nothing else\n // in this walk needs the arch delta, so the read itself sits behind\n // `grading` — and it is NOT always cached: `validate --feature` passes a\n // context, but archive's plan gate (commands/archive/plan/gate.ts) calls\n // featureCoherence without one, so this parse is paid fresh there.\n if (grading && existsSync(paths.archSpec)) {\n const archReqs = context === undefined\n ? parseRequirements(await readFile(paths.archSpec, \"utf8\"))\n : await context.readRequirements(paths.archSpec);\n const archTarget = { where: `${svc}: arch.spec.md`, subject: svc };\n issues.push(...capabilityUnknownIssues(archReqs, archTarget, capabilityVocab));\n issues.push(...realizesUnknownIssues(archReqs, archTarget, await capabilityReqs()));\n }\n // Only operations genuinely NEW to this service count as feature-added: authors\n // restate the full living API in the delta file (it is a complete document, not a patch).\n const featDoc = await readOpenapi(paths.openapi, context);\n // A feature contract that EXISTS but does not read is a broken document,\n // not an empty one — the living-side `openapi.invalid` discipline\n // (validate/service/api.ts), applied to the delta. Before this check,\n // `validate --feature` said nothing about the file at all: the empty\n // parse silently skipped every baseline pin and removal marker in it,\n // and E1 graded requirements against a contract that was never read.\n // The rest of this service's contract-axis checks are suspended — every\n // one of them would be an opinion about a document nobody could open.\n if (featDoc.unreadable) {\n unreadableApis.add(svc);\n issues.push({\n severity: \"error\",\n code: \"openapi.invalid\",\n subject: svc,\n message: `${svc}: this feature's openapi.yaml does not parse${featDoc.error === undefined ? \"\" : ` (${featDoc.error})`} — the contract axis is unchecked and the merge would have nothing true to write. Fix the YAML first.`,\n });\n continue;\n }\n const featOps = featDoc.ops;\n const removals = featOps.filter((op) => op.remove);\n // A relocation — same operationId, removal marker on the old slot, upsert\n // on the new one — retires nothing. Every rule that asks \"is this operation\n // going away\" must therefore ask the NET set (markers this feature does not\n // redefine), while \"did the author write a marker at all\" asks the raw one.\n // Conflating them made moving an endpoint fail three checks at once for a\n // change that removes nothing.\n const redefined = new Set(featOps.filter((op) => !op.remove).map((op) => op.id));\n const markerIds = new Set(removals.map((op) => op.id));\n const netRemoved = new Set([...markerIds].filter((id) => !redefined.has(id)));\n removingOps.set(svc, netRemoved);\n allMarkerIds.set(svc, markerIds);\n anonymousMarkers.set(svc, featDoc.anonymousRemovals.length > 0);\n\n // A marker with no operationId names a slot but no operation. Every\n // id-keyed check is blind to it, so it used to travel all the way into the\n // living contract as a literal `x-loam-remove: true` — the one feature-only\n // key that must never be published.\n for (const marker of featDoc.anonymousRemovals) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-marker-anonymous\",\n subject: svc,\n message: `${svc}: ${marker.method} ${marker.path} carries x-loam-remove: true but declares no operationId — loam cannot tell which operation it retires; name the operation the living contract has at that slot`,\n });\n }\n\n const livingDoc = await readOpenapi((await locateServicePaths(docsDir, svc, context)).openapi, context);\n const livingOps = livingDoc.ops;\n for (const id of livingDoc.duplicateIds) {\n const slots = livingOps.filter((op) => op.id === id).map((op) => `${op.method} ${op.path}`);\n issues.push({\n severity: \"warn\",\n code: \"openapi.duplicate-operationid\",\n subject: svc,\n message: `${svc}: the living OpenAPI defines operationId '${id}' at ${slots.join(\" and \")} — every join on the id (a requirement's Operations: line, an edge's metadata { op }, a removal marker) picks one of those slots arbitrarily`,\n });\n }\n // The baseline axis — operation pins, path-item keys and components —\n // graded in its own package (openapi/baseline/gate.ts), slot-keyed\n // exactly as the merge writes. Texts come through the context's memoized\n // read when there is one, mirroring the readRequirements pattern above;\n // the gate itself never touches the filesystem.\n if (existsSync(paths.openapi)) {\n const livingOpenapiPath = (await locateServicePaths(docsDir, svc, context)).openapi;\n // `context.readText` DECODES and THROWS on non-UTF-8 bytes, where the\n // no-context `readFile` substitutes U+FFFD and lets `readOpenapi`\n // flag the document unreadable. Uncaught, one service's UTF-16 living\n // contract took down the whole feature target under validate's\n // context while archive's no-context path sailed past — the exact\n // context/no-context divergence doc.ts documents as paid for once.\n // Either text failing to decode gets the same answer the gate gives\n // an unreadable parse: that side's surface checks are skipped.\n const readOr = async (p: string): Promise<string | undefined> => {\n try {\n return context === undefined ? await readFile(p, \"utf8\") : await context.readText(p);\n } catch (err) {\n if (err instanceof NotUtf8DocumentError) return undefined;\n throw err;\n }\n };\n const featureText = await readOr(paths.openapi);\n const livingText = !existsSync(livingOpenapiPath) ? undefined : await readOr(livingOpenapiPath);\n if (featureText !== undefined) {\n issues.push(\n ...openapiBaselineIssues({ featDoc, livingDoc, featureText, livingText, service: svc, featureId }),\n );\n }\n }\n\n if (featOps.length > 0) {\n const living = new Set(livingOps.filter((op) => !op.remove).map((op) => op.id));\n for (const op of featOps) {\n if (!op.remove && !living.has(op.id)) featureApiOps.add(op.id);\n }\n\n // Removal is exact: the feature names both an operationId and the\n // path+method slot it expects to delete. That catches stale deltas whose\n // target moved or was already retired before the archive planner runs.\n const justified = removedReqOps.get(svc) ?? new Set<string>();\n for (const marker of removals) {\n const target = livingOps.find((op) => op.path === marker.path && op.method === marker.method);\n if (target === undefined) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-target-missing\",\n subject: svc,\n message: `${svc}: removal marker for '${marker.id}' addresses ${marker.method} ${marker.path}, but no living operation exists there`,\n });\n } else if (target.id !== marker.id) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-target-mismatch\",\n subject: svc,\n message: `${svc}: removal marker names '${marker.id}' at ${marker.method} ${marker.path}, but the living operation there is '${target.id}'`,\n });\n }\n // A relocation needs no REMOVED requirement: the requirement governing\n // the operation stays, the operation only changes address.\n if (!netRemoved.has(marker.id)) continue;\n if (!justified.has(marker.id)) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-marker-unjustified\",\n subject: svc,\n message: `${svc}: removal marker for '${marker.id}' is not governed by a REMOVED requirement's Operations: line`,\n });\n }\n }\n }\n }\n for (const [svc, required] of removedReqOps) {\n // The markers live in the unreadable file — \"no matching marker\" would\n // be a claim about a document nobody could open.\n if (unreadableApis.has(svc)) continue;\n const marked = allMarkerIds.get(svc) ?? new Set<string>();\n for (const op of required) {\n if (marked.has(op)) continue;\n // Distinguish \"no marker at all\" from \"a marker is there but loam cannot\n // read which operation it names\" — the first asks the author to write the\n // marker, the second to write the operationId, and telling somebody who\n // already wrote the marker that there is none sends them looking for a\n // file they are staring at.\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-marker-missing\",\n subject: svc,\n message: anonymousMarkers.get(svc) === true\n ? `${svc}: REMOVED requirement governs '${op}', and its feature openapi.yaml carries an x-loam-remove: true marker with no operationId — name '${op}' on that marker`\n : `${svc}: REMOVED requirement governs '${op}', but its feature openapi.yaml has no matching x-loam-remove: true marker`,\n });\n }\n }\n return { reqOps, removedReqOps, removingOps, allMarkerIds, anonymousMarkers, featureApiOps, unreadableApis };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"declared.js","sourceRoot":"","sources":["../../../src/core/coherence/declared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a feature's own delta DECLARES, per service, indexed once.\n *\n * Nine indexes over the same four files (the delta spec.md, arch.spec.md,\n * openapi.yaml, and the living contract they are graded against), built in one\n * walk because every check downstream joins on the service id and would\n * otherwise re-read the delta per question. The distinctions here are the ones\n * the checks cannot make for themselves: an operation a REMOVED requirement\n * governs is being retired, so it neither claims the contract nor governs\n * anything after the merge; a removal marker that names an id also defined\n * elsewhere in the delta is a RELOCATION, not a retirement; a living operation\n * another one replaces at its address, with no marker naming it, leaves the\n * contract all the same; and a marker with\n * no `operationId` at all is a shape only this walk can see. The capability\n * gate rides the same loop: both delta spec documents are graded against the\n * fleet vocabulary here, because coherence.ts and this package's file count\n * are both at their limits and the walk already holds the parsed documents.\n */\nimport { existsSync } from \"node:fs\";\nimport { readOr } from \"../openapi/contract-text.js\";\nimport { readFile } from \"node:fs/promises\";\nimport { type PathableService } from \"../kernel/ids/service.js\";\nimport type { Issue } from \"../vocabulary/issue.js\";\nimport { featureSpecPaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport { readCapabilityVocabulary } from \"../capabilities/capabilities.js\";\nimport { capabilityRequirementIndex } from \"../capabilities/findings.js\";\nimport { withFeatureCapabilities, withFeatureRequirements } from \"../capabilities/delta/overlay.js\";\nimport { featureCapabilityDeltas } from \"../capabilities/delta/tree.js\";\nimport { realizesUnknownIssues } from \"../capabilities/realizes/findings.js\";\nimport type { CapabilityRequirementIndex } from \"../capabilities/realizes/join.js\";\nimport { capabilityUnknownIssues } from \"../capabilities/findings.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport { openapiBaselineIssues } from \"../openapi/baseline/gate.js\";\nimport { readOpenapi } from \"../openapi/doc.js\";\nimport { operationRetirements } from \"./retired/retirement.js\";\nimport { operationArrivals, type Arrival, type Displacement } from \"./retired/revival/arrivals.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\n\n/** Everything one feature's delta says about its own services. */\nexport interface Declared {\n /** Per service: the operations its non-REMOVED requirements govern. */\n reqOps: Map<PathableService, string[]>;\n /** Per service: the operations its REMOVED requirements governed. */\n removedReqOps: Map<PathableService, Set<string>>;\n /** Per service: operations this feature genuinely retires (markers minus relocations). */\n removingOps: Map<string, Set<string>>;\n /** Per service: every id a removal marker names, relocations included. */\n allMarkerIds: Map<string, Set<string>>;\n /** Per service: does the feature contract carry a marker with no operationId? */\n anonymousMarkers: Map<string, boolean>;\n /** The operationIds the living contract of their service does not hold, across the whole feature — new, or revived (see `revivalCandidates`). */\n featureApiOps: Set<string>;\n /**\n * Services whose FEATURE openapi.yaml exists but does not parse. Every check\n * that joins on the feature contract must suspend itself for these — an\n * unreadable document yields the same empty op set as an absent one, and\n * grading against that emptiness produced false errors pointing everywhere\n * but at the broken file.\n */\n unreadableApis: Set<string>;\n /**\n * Per service: every (operationId, address) the contract the archive would\n * write has and the living one lacks — an addition, or a revival of what an\n * archived feature retired or moved away, which the living contract alone\n * cannot tell apart. coherence.ts asks the retired index (./retired/) only\n * when one of these lists is non-empty.\n */\n revivalCandidates: Map<PathableService, Arrival[]>;\n /**\n * Per service: every living operationId the contract the archive would\n * write answers nowhere while no removal marker names it — replaced at its\n * address by another operation (./retired/revival/arrivals.ts). The\n * removal gate's consumers are asked of these too: the loss is the same,\n * only the spelling differs.\n */\n displacedOps: Map<PathableService, Displacement[]>;\n}\n\n/** The three facts this walk needs about the feature it is reading. */\nexport interface DeltaScope {\n docsDir: DocsDir;\n featureDir: FeatureDir;\n featureId: string;\n}\n\nexport async function declaredByService(\n scope: DeltaScope,\n svcNames: PathableService[],\n issues: Issue[],\n context?: FleetContext,\n): Promise<Declared> {\n const { docsDir, featureDir, featureId } = scope;\n const reqOps = new Map<PathableService, string[]>();\n const removedReqOps = new Map<PathableService, Set<string>>();\n /** Per service: operations this feature genuinely retires (markers minus relocations). */\n const removingOps = new Map<string, Set<string>>();\n /** Per service: every id a removal marker names, relocations included. */\n const allMarkerIds = new Map<string, Set<string>>();\n /** Per service: does the feature contract carry a marker with no operationId? */\n const anonymousMarkers = new Map<string, boolean>();\n const featureApiOps = new Set<string>();\n const unreadableApis = new Set<string>();\n const revivalCandidates = new Map<PathableService, Arrival[]>();\n const displacedOps = new Map<PathableService, Displacement[]>();\n /** Per service read here: the operationIds its living contract holds, or null when it does not read. */\n const livingIds = new Map<string, ReadonlySet<string> | null>();\n // The capability gate's vocabulary, once per call. Absent or invalid stays\n // SILENT here — capability.invalid is validate --all's one finding about a\n // broken file, and an absent file means the fleet never opted in — so with\n // either, nothing new gates archive (the roadmap's own trade; the\n // /loam-check row tells agents to fix the YAML first). `grading` is that\n // silence hoisted into a guard, so a silent axis also skips the reads that\n // exist only to feed it.\n //\n // The vocabulary is the one this feature's MERGE would leave behind, not\n // the one on disk: a feature that adds a capability requirement in its own\n // capability delta and `Realizes:` it from its own service delta is the\n // flow the whole axis exists for, and graded against the living tree alone\n // it is refused with `capability.realizes-unknown` — an error, gating\n // archive, for a target the same archive creates. `capabilities/delta/\n // overlay.ts` states the whole judgement. One `existsSync` for a feature\n // that carries no capability delta, which is every feature in a fleet that\n // has not adopted the axis.\n const capabilityDeltas =\n context === undefined ? await featureCapabilityDeltas(featureDir) : await context.featureCapabilityDeltas(featureDir);\n const capabilityVocab = withFeatureCapabilities(\n context === undefined ? await readCapabilityVocabulary(docsDir) : await context.capabilities(docsDir),\n capabilityDeltas.docs.map((d) => d.id),\n );\n const grading = capabilityVocab.present && capabilityVocab.invalid === undefined;\n // The `Realizes:` index, built at most ONCE per feature and only if some\n // delta document actually reaches the grading branch. Lazy rather than\n // eager because it reads every capability document in the fleet, and a\n // feature whose deltas carry no requirement file at all must not pay for\n // that — the same \"a silent axis skips the reads that feed it\" rule\n // `grading` above states, one level deeper.\n let pendingIndex: Promise<CapabilityRequirementIndex> | null = null;\n const capabilityReqs = (): Promise<CapabilityRequirementIndex> => {\n const read = context === undefined\n ? async (p: string): Promise<Requirement[]> => parseRequirements(await readFile(p, \"utf8\"))\n : (p: string): Promise<Requirement[]> => context.readRequirements(p);\n // The living index first, then widened by what this feature's capability\n // deltas ADD or MODIFY. Both halves are inside the lazy thunk, so a\n // feature whose deltas carry no requirement file at all pays for neither.\n pendingIndex ??= capabilityRequirementIndex(capabilityVocab, read).then(async (index) =>\n withFeatureRequirements(\n index,\n await Promise.all(capabilityDeltas.docs.map(async (d) => ({ id: d.id, reqs: await read(d.spec) }))),\n ),\n );\n return pendingIndex;\n };\n for (const svc of svcNames) {\n const paths = featureSpecPaths(featureDir, svc);\n if (existsSync(paths.spec)) {\n const reqs = context === undefined\n ? parseRequirements(await readFile(paths.spec, \"utf8\"))\n : await context.readRequirements(paths.spec);\n // REMOVED requirements are being retired along with their operations — their\n // ops neither claim the contract (E1) nor govern anything after the merge.\n reqOps.set(svc, reqs.filter((r) => r.kind !== \"REMOVED\").flatMap((r) => r.operations));\n removedReqOps.set(\n svc,\n new Set(reqs.filter((r) => r.kind === \"REMOVED\").flatMap((r) => r.operations)),\n );\n if (grading) {\n const target = { where: `${svc}: spec.md`, subject: svc };\n issues.push(...capabilityUnknownIssues(reqs, target, capabilityVocab));\n issues.push(...realizesUnknownIssues(reqs, target, await capabilityReqs()));\n }\n }\n // The delta arch.spec.md carries the same grammar and merges the same\n // way, so an undeclared capability in it gates identically. Nothing else\n // in this walk needs the arch delta, so the read itself sits behind\n // `grading` — and it is NOT always cached: `validate --feature` passes a\n // context, but archive's plan gate (commands/archive/plan/gate.ts) calls\n // featureCoherence without one, so this parse is paid fresh there.\n if (grading && existsSync(paths.archSpec)) {\n const archReqs = context === undefined\n ? parseRequirements(await readFile(paths.archSpec, \"utf8\"))\n : await context.readRequirements(paths.archSpec);\n const archTarget = { where: `${svc}: arch.spec.md`, subject: svc };\n issues.push(...capabilityUnknownIssues(archReqs, archTarget, capabilityVocab));\n issues.push(...realizesUnknownIssues(archReqs, archTarget, await capabilityReqs()));\n }\n // Only operations the living contract does not hold count as feature-added:\n // a delta may restate living operations it does not change (a quote), and\n // a quote adds nothing. \"Not in living\" is not quite \"new\", though — an\n // operation an archived feature RETIRED is absent too, and adding it back\n // is `openapi.op-revived`'s question (./retired/revival/revived.ts), asked of the\n // `revivalCandidates` collected below.\n const featDoc = await readOpenapi(paths.openapi, context);\n // A feature contract that EXISTS but does not read is a broken document,\n // not an empty one — the living-side `openapi.invalid` discipline\n // (validate/service/api.ts), applied to the delta. Before this check,\n // `validate --feature` said nothing about the file at all: the empty\n // parse silently skipped every baseline pin and removal marker in it,\n // and E1 graded requirements against a contract that was never read.\n // The rest of this service's contract-axis checks are suspended — every\n // one of them would be an opinion about a document nobody could open.\n if (featDoc.unreadable) {\n unreadableApis.add(svc);\n issues.push({\n severity: \"error\",\n code: \"openapi.invalid\",\n subject: svc,\n message: `${svc}: this feature's openapi.yaml does not parse${featDoc.error === undefined ? \"\" : ` (${featDoc.error})`} — the contract axis is unchecked and the merge would have nothing true to write. Fix the YAML first.`,\n });\n continue;\n }\n const featOps = featDoc.ops;\n const removals = featOps.filter((op) => op.remove);\n // The raw marker set and the NET one (markers minus relocations) —\n // ./retired/retirement.ts owns the rule and says why the two differ; the\n // retired index reads every archived feature through the same function.\n const { markerIds, net: netRemoved } = operationRetirements(featOps);\n removingOps.set(svc, netRemoved);\n allMarkerIds.set(svc, markerIds);\n anonymousMarkers.set(svc, featDoc.anonymousRemovals.length > 0);\n\n // A marker with no operationId names a slot but no operation. Every\n // id-keyed check is blind to it, so it used to travel all the way into the\n // living contract as a literal `x-loam-remove: true` — the one feature-only\n // key that must never be published.\n for (const marker of featDoc.anonymousRemovals) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-marker-anonymous\",\n subject: svc,\n message: `${svc}: ${marker.method} ${marker.path} carries x-loam-remove: true but declares no operationId — loam cannot tell which operation it retires; name the operation the living contract has at that slot`,\n });\n }\n\n const livingOpenapiPath = (await locateServicePaths(docsDir, svc, context)).openapi;\n const livingDoc = await readOpenapi(livingOpenapiPath, context);\n const livingOps = livingDoc.ops;\n livingIds.set(svc, livingDoc.unreadable ? null : new Set(livingOps.filter((op) => !op.remove).map((op) => op.id)));\n const sides = { featPath: paths.openapi, featDoc, livingPath: livingOpenapiPath, livingDoc, service: svc };\n const moved = await operationArrivals(sides, context);\n revivalCandidates.set(svc, moved.operations);\n if (moved.displaced.length > 0) displacedOps.set(svc, moved.displaced);\n for (const id of livingDoc.duplicateIds) {\n const slots = livingOps.filter((op) => op.id === id).map((op) => `${op.method} ${op.path}`);\n issues.push({\n severity: \"warn\",\n code: \"openapi.duplicate-operationid\",\n subject: svc,\n message: `${svc}: the living OpenAPI defines operationId '${id}' at ${slots.join(\" and \")} — every join on the id (a requirement's Operations: line, an edge's metadata { op }, a removal marker) picks one of those slots arbitrarily`,\n });\n }\n // The baseline axis — operation pins, path-item keys and components —\n // graded in its own package (openapi/baseline/gate.ts), slot-keyed\n // exactly as the merge writes. Texts come through the context's memoized\n // read when there is one, mirroring the readRequirements pattern above;\n // the gate itself never touches the filesystem.\n if (existsSync(paths.openapi)) {\n // `readOr` (../openapi/contract-text.ts, which says why): `context.readText`\n // throws on non-UTF-8 bytes where the no-context `readFile` lets\n // `readOpenapi` flag the document unreadable — the exact\n // context/no-context divergence doc.ts documents as paid for once.\n // Either text failing to decode gets the same answer the gate gives\n // an unreadable parse: that side's surface checks are skipped.\n const featureText = await readOr(paths.openapi, context);\n const livingText = !existsSync(livingOpenapiPath) ? undefined : await readOr(livingOpenapiPath, context);\n if (featureText !== undefined) {\n issues.push(\n ...openapiBaselineIssues({ featDoc, livingDoc, featureText, livingText, service: svc, featureId }),\n );\n }\n }\n\n if (featOps.length > 0) {\n const living = new Set(livingOps.filter((op) => !op.remove).map((op) => op.id));\n for (const op of featOps) {\n if (!op.remove && !living.has(op.id)) featureApiOps.add(op.id);\n }\n\n // Removal is exact: the feature names both an operationId and the\n // path+method slot it expects to delete. That catches stale deltas whose\n // target moved or was already retired before the archive planner runs.\n const justified = removedReqOps.get(svc) ?? new Set<string>();\n for (const marker of removals) {\n const target = livingOps.find((op) => op.path === marker.path && op.method === marker.method);\n if (target === undefined) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-target-missing\",\n subject: svc,\n message: `${svc}: removal marker for '${marker.id}' addresses ${marker.method} ${marker.path}, but no living operation exists there`,\n });\n } else if (target.id !== marker.id) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-target-mismatch\",\n subject: svc,\n message: `${svc}: removal marker names '${marker.id}' at ${marker.method} ${marker.path}, but the living operation there is '${target.id}'`,\n });\n }\n // A relocation needs no REMOVED requirement: the requirement governing\n // the operation stays, the operation only changes address.\n if (!netRemoved.has(marker.id)) continue;\n if (!justified.has(marker.id)) {\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-marker-unjustified\",\n subject: svc,\n message: `${svc}: removal marker for '${marker.id}' is not governed by a REMOVED requirement's Operations: line`,\n });\n }\n }\n }\n }\n for (const [svc, required] of removedReqOps) {\n // The markers live in the unreadable file — \"no matching marker\" would\n // be a claim about a document nobody could open.\n if (unreadableApis.has(svc)) continue;\n const marked = allMarkerIds.get(svc) ?? new Set<string>();\n const replaced = new Set((displacedOps.get(svc) ?? []).map((d) => d.id));\n const living = livingIds.get(svc);\n for (const op of required) {\n if (marked.has(op)) continue;\n // A marker is owed only while there is a living operation for it to\n // retire — the event axis's rule (events/declared.ts) on the contract\n // axis. An operation this delta already REPLACES at its address leaves\n // the contract without one, and the marker could not even be written:\n // the slot holds the replacement. So the route the overwrite's\n // `openapi.remove-op-consumed` names — REMOVE the requirement governing\n // the old id — ended in this error, followable only by deleting the\n // requirement's Operations: line, which nothing said (review\n // 2026-10-12, finding 4). And an id the service's living contract does\n // not define is not this service's to mark at all: a REMOVED consumer\n // requirement naming ANOTHER service's operation was told to retire it\n // from its own contract. Both exemptions are facts about a contract\n // loam READ: a living openapi.yaml that does not parse proves neither,\n // so the marker stays owed — the closed direction, and the answer this\n // check gave before either exemption existed. Skipping it there let a\n // REMOVED requirement with no marker pass at exit 0 (the contract\n // review, finding 3); `openapi.invalid` is still validate's finding\n // about the file itself.\n if (replaced.has(op) || (living !== undefined && living !== null && !living.has(op))) continue;\n // Distinguish \"no marker at all\" from \"a marker is there but loam cannot\n // read which operation it names\" — the first asks the author to write the\n // marker, the second to write the operationId, and telling somebody who\n // already wrote the marker that there is none sends them looking for a\n // file they are staring at.\n issues.push({\n severity: \"error\",\n code: \"openapi.remove-marker-missing\",\n subject: svc,\n message: anonymousMarkers.get(svc) === true\n ? `${svc}: REMOVED requirement governs '${op}', and its feature openapi.yaml carries an x-loam-remove: true marker with no operationId — name '${op}' on that marker`\n : `${svc}: REMOVED requirement governs '${op}', but its feature openapi.yaml has no matching x-loam-remove: true marker`,\n });\n }\n }\n return {\n reqOps, removedReqOps, removingOps, allMarkerIds, anonymousMarkers, featureApiOps, unreadableApis, revivalCandidates,\n displacedOps,\n };\n}\n"]}
|