@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
|
@@ -26,6 +26,7 @@ import { readCapabilityVocabulary } from "./capabilities/capabilities.js";
|
|
|
26
26
|
import { featureCapabilityDeltas } from "./capabilities/delta/tree.js";
|
|
27
27
|
import { conflictMarkerLines } from "./conflict-markers.js";
|
|
28
28
|
import { decodeDocument } from "./kernel/document-bytes.js";
|
|
29
|
+
import { memo } from "./kernel/concurrency.js";
|
|
29
30
|
import { loadFile } from "./c4/likec4.js";
|
|
30
31
|
import { loadBatch } from "./c4/workspace.js";
|
|
31
32
|
import { ServiceModels } from "./c4/prefetch/fleet.js";
|
|
@@ -78,6 +79,19 @@ export class FleetContext {
|
|
|
78
79
|
stats() {
|
|
79
80
|
return { ...this.counts };
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* `memo`, counting the miss under `counter` — every read below is this one
|
|
84
|
+
* call. The count is taken when the load STARTS, inside the miss, so a
|
|
85
|
+
* second asker of an in-flight read is a hit and is never counted: the
|
|
86
|
+
* counters mean "work this invocation paid for", which is what the stats
|
|
87
|
+
* tests pin per axis.
|
|
88
|
+
*/
|
|
89
|
+
counted(store, k, counter, make) {
|
|
90
|
+
return memo(store, k, () => {
|
|
91
|
+
this.counts[counter] += 1;
|
|
92
|
+
return make();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
81
95
|
/**
|
|
82
96
|
* The classified `services/` tree — the ONE enumeration this invocation
|
|
83
97
|
* performs. `listServices` below derives its entries from this memo, so a
|
|
@@ -87,67 +101,34 @@ export class FleetContext {
|
|
|
87
101
|
* where the walk now happens.
|
|
88
102
|
*/
|
|
89
103
|
fleetTree(docsDir) {
|
|
90
|
-
|
|
91
|
-
let pending = this.trees.get(k);
|
|
92
|
-
if (pending === undefined) {
|
|
93
|
-
this.counts.serviceEnumerations += 1;
|
|
94
|
-
pending = listFleetTree(docsDir);
|
|
95
|
-
this.trees.set(k, pending);
|
|
96
|
-
}
|
|
97
|
-
return pending;
|
|
104
|
+
return this.counted(this.trees, key(docsDir), "serviceEnumerations", () => listFleetTree(docsDir));
|
|
98
105
|
}
|
|
99
106
|
listServices(docsDir) {
|
|
100
|
-
|
|
101
|
-
let pending = this.services.get(k);
|
|
102
|
-
if (pending === undefined) {
|
|
103
|
-
pending = this.fleetTree(docsDir).then(serviceEntries);
|
|
104
|
-
this.services.set(k, pending);
|
|
105
|
-
}
|
|
106
|
-
return pending;
|
|
107
|
+
return memo(this.services, key(docsDir), () => this.fleetTree(docsDir).then(serviceEntries));
|
|
107
108
|
}
|
|
108
109
|
listFeatures(docsDir, opts = {}) {
|
|
109
110
|
const k = `${key(docsDir)}\0${opts.includeArchived === true ? "all" : "active"}`;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const featureKey = key(entry.dir);
|
|
118
|
-
if (!this.featureServices.has(featureKey)) {
|
|
119
|
-
this.featureServices.set(featureKey, Promise.resolve(entry.services));
|
|
120
|
-
}
|
|
111
|
+
return this.counted(this.features, k, "featureEnumerations", () => listFeatures(docsDir, opts).then((entries) => {
|
|
112
|
+
// listFeatures has already enumerated every feature's specs/ directory;
|
|
113
|
+
// seed the narrower lookup so validation/coherence do not read it again.
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const featureKey = key(entry.dir);
|
|
116
|
+
if (!this.featureServices.has(featureKey)) {
|
|
117
|
+
this.featureServices.set(featureKey, Promise.resolve(entry.services));
|
|
121
118
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
return pending;
|
|
119
|
+
}
|
|
120
|
+
return entries;
|
|
121
|
+
}));
|
|
127
122
|
}
|
|
128
123
|
featureSpecServices(featureDir) {
|
|
129
|
-
|
|
130
|
-
let pending = this.featureServices.get(k);
|
|
131
|
-
if (pending === undefined) {
|
|
132
|
-
this.counts.featureServiceEnumerations += 1;
|
|
133
|
-
pending = featureSpecServices(featureDir);
|
|
134
|
-
this.featureServices.set(k, pending);
|
|
135
|
-
}
|
|
136
|
-
return pending;
|
|
124
|
+
return this.counted(this.featureServices, key(featureDir), "featureServiceEnumerations", () => featureSpecServices(featureDir));
|
|
137
125
|
}
|
|
138
126
|
/**
|
|
139
127
|
* A markdown artifact as text. Bytes in, decoded once, and refused rather
|
|
140
128
|
* than degraded when they are not UTF-8 — see NotUtf8DocumentError.
|
|
141
129
|
*/
|
|
142
130
|
readText(path) {
|
|
143
|
-
|
|
144
|
-
let pending = this.texts.get(k);
|
|
145
|
-
if (pending === undefined) {
|
|
146
|
-
this.counts.textReads += 1;
|
|
147
|
-
pending = readFile(path).then((bytes) => decodeDocument(bytes, path));
|
|
148
|
-
this.texts.set(k, pending);
|
|
149
|
-
}
|
|
150
|
-
return pending;
|
|
131
|
+
return this.counted(this.texts, key(path), "textReads", () => readFile(path).then((bytes) => decodeDocument(bytes, path)));
|
|
151
132
|
}
|
|
152
133
|
/**
|
|
153
134
|
* Git conflict markers in an already-read document, by line. Free for any
|
|
@@ -159,34 +140,13 @@ export class FleetContext {
|
|
|
159
140
|
return conflictMarkerLines(await this.readText(path));
|
|
160
141
|
}
|
|
161
142
|
readRequirements(path) {
|
|
162
|
-
|
|
163
|
-
let pending = this.requirements.get(k);
|
|
164
|
-
if (pending === undefined) {
|
|
165
|
-
this.counts.requirementParses += 1;
|
|
166
|
-
pending = this.readText(path).then(parseRequirements);
|
|
167
|
-
this.requirements.set(k, pending);
|
|
168
|
-
}
|
|
169
|
-
return pending;
|
|
143
|
+
return this.counted(this.requirements, key(path), "requirementParses", () => this.readText(path).then(parseRequirements));
|
|
170
144
|
}
|
|
171
145
|
readOpenapi(path) {
|
|
172
|
-
|
|
173
|
-
let pending = this.openapis.get(k);
|
|
174
|
-
if (pending === undefined) {
|
|
175
|
-
this.counts.openapiParses += 1;
|
|
176
|
-
pending = readOpenapi(path);
|
|
177
|
-
this.openapis.set(k, pending);
|
|
178
|
-
}
|
|
179
|
-
return pending;
|
|
146
|
+
return this.counted(this.openapis, key(path), "openapiParses", () => readOpenapi(path));
|
|
180
147
|
}
|
|
181
148
|
readAsyncapi(path) {
|
|
182
|
-
|
|
183
|
-
let pending = this.asyncapis.get(k);
|
|
184
|
-
if (pending === undefined) {
|
|
185
|
-
this.counts.asyncapiParses += 1;
|
|
186
|
-
pending = readAsyncapi(path);
|
|
187
|
-
this.asyncapis.set(k, pending);
|
|
188
|
-
}
|
|
189
|
-
return pending;
|
|
149
|
+
return this.counted(this.asyncapis, key(path), "asyncapiParses", () => readAsyncapi(path));
|
|
190
150
|
}
|
|
191
151
|
/**
|
|
192
152
|
* The capability vocabulary, read once per invocation. Direction rule,
|
|
@@ -196,14 +156,7 @@ export class FleetContext {
|
|
|
196
156
|
* why the functions there take plain data or a `read` function, never a FleetContext.
|
|
197
157
|
*/
|
|
198
158
|
capabilities(docsDir) {
|
|
199
|
-
|
|
200
|
-
let pending = this.capabilityVocabularies.get(k);
|
|
201
|
-
if (pending === undefined) {
|
|
202
|
-
this.counts.capabilityParses += 1;
|
|
203
|
-
pending = readCapabilityVocabulary(docsDir);
|
|
204
|
-
this.capabilityVocabularies.set(k, pending);
|
|
205
|
-
}
|
|
206
|
-
return pending;
|
|
159
|
+
return this.counted(this.capabilityVocabularies, key(docsDir), "capabilityParses", () => readCapabilityVocabulary(docsDir));
|
|
207
160
|
}
|
|
208
161
|
/**
|
|
209
162
|
* One feature's `capabilities/` delta tree, walked once per invocation. Two
|
|
@@ -217,14 +170,7 @@ export class FleetContext {
|
|
|
217
170
|
* FleetContext, so `src/core/capabilities/` never imports this module back.
|
|
218
171
|
*/
|
|
219
172
|
featureCapabilityDeltas(featureDir) {
|
|
220
|
-
|
|
221
|
-
let pending = this.featureCapabilities.get(k);
|
|
222
|
-
if (pending === undefined) {
|
|
223
|
-
this.counts.featureCapabilityWalks += 1;
|
|
224
|
-
pending = featureCapabilityDeltas(featureDir);
|
|
225
|
-
this.featureCapabilities.set(k, pending);
|
|
226
|
-
}
|
|
227
|
-
return pending;
|
|
173
|
+
return this.counted(this.featureCapabilities, key(featureDir), "featureCapabilityWalks", () => featureCapabilityDeltas(featureDir));
|
|
228
174
|
}
|
|
229
175
|
async operations(path) {
|
|
230
176
|
return (await this.readOpenapi(path)).ops;
|
|
@@ -244,14 +190,7 @@ export class FleetContext {
|
|
|
244
190
|
* this class was ever needed for.
|
|
245
191
|
*/
|
|
246
192
|
loadLikeC4(path) {
|
|
247
|
-
|
|
248
|
-
let pending = this.likec4.get(k);
|
|
249
|
-
if (pending === undefined) {
|
|
250
|
-
this.counts.likec4Loads += 1;
|
|
251
|
-
pending = loadFile(path);
|
|
252
|
-
this.likec4.set(k, pending);
|
|
253
|
-
}
|
|
254
|
-
return pending;
|
|
193
|
+
return this.counted(this.likec4, key(path), "likec4Loads", () => loadFile(path));
|
|
255
194
|
}
|
|
256
195
|
/**
|
|
257
196
|
* Parse many `.likec4` documents through ONE shared workspace (`loadBatch`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. Both\n * RULES live in leaves — the decode in `document-bytes.ts`, the marker scan\n * and its findings in `conflict-markers.ts` — because the parsers and graders\n * that touch a single file without an index owe the same rules, and must not\n * have to import this module — and the fleet it pulls in behind it — to obey\n * them.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readAsyncapi } from \"./asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"./asyncapi/model.js\";\nimport { readCapabilityVocabulary, type CapabilityVocabulary } from \"./capabilities/capabilities.js\";\nimport { featureCapabilityDeltas } from \"./capabilities/delta/tree.js\";\nimport type { CapabilityTree } from \"./capabilities/tree.js\";\nimport { conflictMarkerLines } from \"./conflict-markers.js\";\nimport { decodeDocument } from \"./kernel/document-bytes.js\";\nimport { loadFile, type LoadedDoc } from \"./c4/likec4.js\";\nimport { loadBatch } from \"./c4/workspace.js\";\nimport { ServiceModels } from \"./c4/prefetch/fleet.js\";\nimport type { ServiceModel } from \"./c4/service-model/load.js\";\nimport type { ModelShape } from \"./c4/service-model/shape.js\";\nimport type { ServicePaths } from \"./repo/paths.js\";\nimport { readOpenapi, type OpenapiDoc, type Operation } from \"./openapi/doc.js\";\nimport type { RawServiceId } from \"./kernel/ids/service.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"./repo/entries.js\";\nimport { featureSpecServices, listFeatures, listFleetTree, serviceEntries } from \"./repo/repo.js\";\nimport type { FleetTree } from \"./repo/tree/walk.js\";\nimport { parseRequirements } from \"./document/parse.js\";\nimport { type Requirement } from \"./document/spec.js\";\nimport type { DocsDir, FeatureDir } from \"./kernel/ids/dirs.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n /**\n * Reads of a service's `asyncapi.yaml`. Counted separately because this axis\n * is the one that makes a per-service run read the whole fleet: \"does anybody\n * publish this message\" has no local answer, so `spine.message-unproduced`\n * walks every service's contract. The memo is what keeps that one walk per\n * command instead of one per consuming edge.\n */\n asyncapiParses: number;\n /**\n * Reads of the capability vocabulary — `architecture/capabilities.yaml` AND\n * the `capabilities/` walk, which are one vocabulary and so one read. One per\n * invocation is the capability axis's exit criterion, and this counter is how\n * a test pins it. (Permissions predates the memo and re-parses per service —\n * do not copy that.)\n */\n capabilityParses: number;\n /**\n * Walks of ONE feature's `capabilities/` delta tree. Counted apart from\n * `capabilityParses` because it answers a different question about a\n * different directory, and because a fleet that has not adopted the business\n * axis must be able to prove it pays nothing: every feature's walk is a\n * single `existsSync` that finds no directory, so this counter rises while\n * `textReads` and `requirementParses` do not.\n */\n featureCapabilityWalks: number;\n likec4Loads: number;\n /**\n * PER-SERVICE PROJECTS parsed — the map plus one extending model, staged\n * together (`c4/service-model/`). Apart from `likec4Loads` because they are\n * different units: that counter means documents parsed alone, and a fleet\n * whose models all extend the map moves every unit to this one. One project\n * per service is the axis's exit criterion, and this is how a test pins it.\n */\n projectLoads: number;\n}\n\nconst key = (path: string): string => resolve(path);\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly trees = new Map<string, Promise<FleetTree>>();\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<RawServiceId[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly asyncapis = new Map<string, Promise<AsyncapiDoc>>();\n private readonly capabilityVocabularies = new Map<string, Promise<CapabilityVocabulary>>();\n private readonly featureCapabilities = new Map<string, Promise<CapabilityTree>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n /**\n * The service-model axis's own memos, one object because the rules they\n * encode are `c4/prefetch/fleet.ts`'s rather than this class's. It counts\n * through this instance's counter, so `stats()` stays the one place a caller\n * reads what an invocation cost.\n */\n private readonly models = new ServiceModels({\n onProjectLoad: () => {\n this.counts.projectLoads += 1;\n },\n known: async (docsDir) => new Set((await this.listServices(docsDir)).map((s) => s.id)),\n standalone: (path) => this.loadLikeC4(path),\n });\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n asyncapiParses: 0,\n capabilityParses: 0,\n featureCapabilityWalks: 0,\n likec4Loads: 0,\n projectLoads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n /**\n * The classified `services/` tree — the ONE enumeration this invocation\n * performs. `listServices` below derives its entries from this memo, so a\n * command that asks for both the fleet and the tree's findings (`validate\n * --all` does) still walks the directories exactly once. The counter keeps\n * its historical meaning — walks of `services/` — it just counts the walk\n * where the walk now happens.\n */\n fleetTree(docsDir: DocsDir): Promise<FleetTree> {\n const k = key(docsDir);\n let pending = this.trees.get(k);\n if (pending === undefined) {\n this.counts.serviceEnumerations += 1;\n pending = listFleetTree(docsDir);\n this.trees.set(k, pending);\n }\n return pending;\n }\n\n listServices(docsDir: DocsDir): Promise<ServiceEntry[]> {\n const k = key(docsDir);\n let pending = this.services.get(k);\n if (pending === undefined) {\n pending = this.fleetTree(docsDir).then(serviceEntries);\n this.services.set(k, pending);\n }\n return pending;\n }\n\n listFeatures(docsDir: DocsDir, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n let pending = this.features.get(k);\n if (pending === undefined) {\n this.counts.featureEnumerations += 1;\n pending = listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n });\n this.features.set(k, pending);\n }\n return pending;\n }\n\n featureSpecServices(featureDir: FeatureDir): Promise<RawServiceId[]> {\n const k = key(featureDir);\n let pending = this.featureServices.get(k);\n if (pending === undefined) {\n this.counts.featureServiceEnumerations += 1;\n pending = featureSpecServices(featureDir);\n this.featureServices.set(k, pending);\n }\n return pending;\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n const k = key(path);\n let pending = this.texts.get(k);\n if (pending === undefined) {\n this.counts.textReads += 1;\n pending = readFile(path).then((bytes) => decodeDocument(bytes, path));\n this.texts.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n const k = key(path);\n let pending = this.requirements.get(k);\n if (pending === undefined) {\n this.counts.requirementParses += 1;\n pending = this.readText(path).then(parseRequirements);\n this.requirements.set(k, pending);\n }\n return pending;\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n const k = key(path);\n let pending = this.openapis.get(k);\n if (pending === undefined) {\n this.counts.openapiParses += 1;\n pending = readOpenapi(path);\n this.openapis.set(k, pending);\n }\n return pending;\n }\n\n readAsyncapi(path: string): Promise<AsyncapiDoc> {\n const k = key(path);\n let pending = this.asyncapis.get(k);\n if (pending === undefined) {\n this.counts.asyncapiParses += 1;\n pending = readAsyncapi(path);\n this.asyncapis.set(k, pending);\n }\n return pending;\n }\n\n /**\n * The capability vocabulary, read once per invocation. Direction rule,\n * load-bearing: this file imports `capabilities/capabilities.js`, so nothing\n * under src/core/capabilities/ may import this module back — that edge is a\n * core-root↔capabilities package cycle `import/no-cycle` cannot see, which is\n * why the functions there take plain data or a `read` function, never a FleetContext.\n */\n capabilities(docsDir: DocsDir): Promise<CapabilityVocabulary> {\n const k = key(docsDir);\n let pending = this.capabilityVocabularies.get(k);\n if (pending === undefined) {\n this.counts.capabilityParses += 1;\n pending = readCapabilityVocabulary(docsDir);\n this.capabilityVocabularies.set(k, pending);\n }\n return pending;\n }\n\n /**\n * One feature's `capabilities/` delta tree, walked once per invocation. Two\n * readers ask for it in the same run — the delta-shape walk\n * (`core/delta/delta.ts`) and the capability overlay\n * (`core/coherence/declared.ts`) — and without the memo a feature carrying\n * three capability deltas paid the readdirs twice.\n *\n * The direction rule stated on `capabilities()` above applies verbatim: the\n * function this stands in front of takes a plain `FeatureDir`, never a\n * FleetContext, so `src/core/capabilities/` never imports this module back.\n */\n featureCapabilityDeltas(featureDir: FeatureDir): Promise<CapabilityTree> {\n const k = key(featureDir);\n let pending = this.featureCapabilities.get(k);\n if (pending === undefined) {\n this.counts.featureCapabilityWalks += 1;\n pending = featureCapabilityDeltas(featureDir);\n this.featureCapabilities.set(k, pending);\n }\n return pending;\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi/doc.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n const k = key(path);\n let pending = this.likec4.get(k);\n if (pending === undefined) {\n this.counts.likec4Loads += 1;\n pending = loadFile(path);\n this.likec4.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Parse many `.likec4` documents through ONE shared workspace (`loadBatch`)\n * and seed the memo above, so every later `loadLikeC4` in this invocation is\n * a hit. Purely an accelerator: the cache is still this instance's — one\n * invocation, one snapshot, no module state — and the counter still means\n * \"documents parsed via LikeC4\", incremented once per batch-parsed document.\n *\n * The failure story is the contract here. A batch-INFRASTRUCTURE failure —\n * a sandboxed runner denying tmpdir writes is the environment class ROADMAP\n * documents — degrades to today's per-path loads by seeding nothing: never\n * an error, and never fewer findings, only the old speed. A path the batch\n * could not stage (missing, unreadable) is simply absent from the result, so\n * the later `loadLikeC4` reproduces today's per-path error exactly. And\n * fewer than two misses returns without a workspace at all: one document\n * gains nothing from batch isolation it already has.\n */\n async prefetchLikeC4(paths: string[]): Promise<void> {\n const missing = [...new Set(paths.map(key))].filter((k) => !this.likec4.has(k));\n if (missing.length < 2) return;\n let docs: Map<string, LoadedDoc>;\n try {\n docs = await loadBatch(missing);\n } catch {\n return;\n }\n for (const [k, doc] of docs) {\n // Keyed by the same `key()` as loadLikeC4 — loadBatch resolves paths the\n // identical way, and `missing` was already resolved, so this holds by\n // construction; the has() guard keeps a concurrent load's promise\n // authoritative rather than double-counting the parse.\n if (this.likec4.has(k)) continue;\n this.counts.likec4Loads += 1;\n this.likec4.set(k, Promise.resolve(doc));\n }\n }\n\n /** The fleet map as the PROJECT it is — landscape plus use cases, minus the generated views file. */\n architecture(docsDir: DocsDir): Promise<LoadedDoc> {\n return this.models.architecture(docsDir);\n }\n\n /**\n * Which shape each of these models has, keyed by resolved path — one read of\n * each file's bytes per invocation. Ask over the FULL enumeration, never a\n * narrowed run's subset: shapes decide which directories are owed a\n * per-service `likec4.config.json`, and a partial map answers \"not\n * standalone\" for every service outside it.\n */\n modelShapes(paths: readonly string[]): Promise<Map<string, ModelShape>> {\n return this.models.shapes(paths);\n }\n\n /**\n * A service's C4 model, read the way its own SHAPE demands — the one entry\n * point for every reader of `model.likec4`, standalone or extending\n * (`c4/service-model/load.ts` states the four arms and why each exists).\n */\n serviceModel(docsDir: DocsDir, paths: ServicePaths): Promise<ServiceModel> {\n return this.models.model(docsDir, paths);\n }\n\n /**\n * Every service's model prepared in two workspaces whatever the fleet's mix:\n * the extending ones as one project each in one of them, the standalone ones\n * through the document batch above. An accelerator with `prefetchLikeC4`'s\n * failure story exactly — a batch that could not be created seeds nothing, so\n * findings never change because of a tmpdir, only the speed does.\n */\n async prefetchServiceModels(docsDir: DocsDir, paths: readonly ServicePaths[]): Promise<void> {\n await this.prefetchLikeC4(await this.models.prefetch(docsDir, paths));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,wBAAwB,EAA6B,MAAM,gCAAgC,CAAC;AACrG,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAIvD,OAAO,EAAE,WAAW,EAAmC,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAwC,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAElG,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAoB,MAAM,oBAAoB,CAAC;AA8CtD,MAAM,GAAG,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpD,mEAAmE;AACnE,MAAM,OAAO,YAAY;IACN,KAAK,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC9C,QAAQ,GAAG,IAAI,GAAG,EAAmC,CAAC;IACtD,QAAQ,GAAG,IAAI,GAAG,EAAmC,CAAC;IACtD,eAAe,GAAG,IAAI,GAAG,EAAmC,CAAC;IAC7D,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC3C,YAAY,GAAG,IAAI,GAAG,EAAkC,CAAC;IACzD,QAAQ,GAAG,IAAI,GAAG,EAA+B,CAAC;IAClD,SAAS,GAAG,IAAI,GAAG,EAAgC,CAAC;IACpD,sBAAsB,GAAG,IAAI,GAAG,EAAyC,CAAC;IAC1E,mBAAmB,GAAG,IAAI,GAAG,EAAmC,CAAC;IACjE,MAAM,GAAG,IAAI,GAAG,EAA8B,CAAC;IAChE;;;;;OAKG;IACc,MAAM,GAAG,IAAI,aAAa,CAAC;QAC1C,aAAa,EAAE,GAAG,EAAE;YAClB,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtF,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;KAC5C,CAAC,CAAC;IAEc,MAAM,GAAsB;QAC3C,mBAAmB,EAAE,CAAC;QACtB,mBAAmB,EAAE,CAAC;QACtB,0BAA0B,EAAE,CAAC;QAC7B,SAAS,EAAE,CAAC;QACZ,iBAAiB,EAAE,CAAC;QACpB,aAAa,EAAE,CAAC;QAChB,cAAc,EAAE,CAAC;QACjB,gBAAgB,EAAE,CAAC;QACnB,sBAAsB,EAAE,CAAC;QACzB,WAAW,EAAE,CAAC;QACd,YAAY,EAAE,CAAC;KAChB,CAAC;IAEF,iFAAiF;IACjF,KAAK;QACH,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED;;;;;;OAMG;IACK,OAAO,CACb,KAA8B,EAC9B,CAAS,EACT,OAAgC,EAChC,IAAsB;QAEtB,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE;YACzB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1B,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,OAAgB;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACrG,CAAC;IAED,YAAY,CAAC,OAAgB;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IAC/F,CAAC;IAED,YAAY,CAAC,OAAgB,EAAE,IAAI,GAAkC,EAAE;QACrE,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,eAAe,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACjF,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAChE,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3C,wEAAwE;YACxE,yEAAyE;YACzE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC1C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACxE,CAAC;YACH,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,mBAAmB,CAAC,UAAsB;QACxC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,CAAC,UAAU,CAAC,EAAE,4BAA4B,EAAE,GAAG,EAAE,CAC5F,mBAAmB,CAAC,UAAU,CAAC,CAChC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,CAC3D,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAC5D,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,eAAe,CAAC,IAAY;QAChC,OAAO,mBAAmB,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,gBAAgB,CAAC,IAAY;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAC1E,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAC5C,CAAC;IACJ,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1F,CAAC;IAED,YAAY,CAAC,IAAY;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7F,CAAC;IAED;;;;;;OAMG;IACH,YAAY,CAAC,OAAgB;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,kBAAkB,EAAE,GAAG,EAAE,CACtF,wBAAwB,CAAC,OAAO,CAAC,CAClC,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,uBAAuB,CAAC,UAAsB;QAC5C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,CAAC,UAAU,CAAC,EAAE,wBAAwB,EAAE,GAAG,EAAE,CAC5F,uBAAuB,CAAC,UAAU,CAAC,CACpC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,OAAO,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,IAAY;QAC7B,OAAO,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED;;;;;;;;;;OAUG;IAEH,UAAU,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACnF,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,cAAc,CAAC,KAAe;QAClC,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QAC/B,IAAI,IAA4B,CAAC;QACjC,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;YAC5B,yEAAyE;YACzE,sEAAsE;YACtE,kEAAkE;YAClE,uDAAuD;YACvD,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,SAAS;YACjC,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,qGAAqG;IACrG,YAAY,CAAC,OAAgB;QAC3B,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;OAMG;IACH,WAAW,CAAC,KAAwB;QAClC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACH,YAAY,CAAC,OAAgB,EAAE,KAAmB;QAChD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,qBAAqB,CAAC,OAAgB,EAAE,KAA8B;QAC1E,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IACxE,CAAC;CACF","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. Both\n * RULES live in leaves — the decode in `document-bytes.ts`, the marker scan\n * and its findings in `conflict-markers.ts` — because the parsers and graders\n * that touch a single file without an index owe the same rules, and must not\n * have to import this module — and the fleet it pulls in behind it — to obey\n * them.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readAsyncapi } from \"./asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"./asyncapi/model.js\";\nimport { readCapabilityVocabulary, type CapabilityVocabulary } from \"./capabilities/capabilities.js\";\nimport { featureCapabilityDeltas } from \"./capabilities/delta/tree.js\";\nimport type { CapabilityTree } from \"./capabilities/tree.js\";\nimport { conflictMarkerLines } from \"./conflict-markers.js\";\nimport { decodeDocument } from \"./kernel/document-bytes.js\";\nimport { memo } from \"./kernel/concurrency.js\";\nimport { loadFile, type LoadedDoc } from \"./c4/likec4.js\";\nimport { loadBatch } from \"./c4/workspace.js\";\nimport { ServiceModels } from \"./c4/prefetch/fleet.js\";\nimport type { ServiceModel } from \"./c4/service-model/load.js\";\nimport type { ModelShape } from \"./c4/service-model/shape.js\";\nimport type { ServicePaths } from \"./repo/paths.js\";\nimport { readOpenapi, type OpenapiDoc, type Operation } from \"./openapi/doc.js\";\nimport type { RawServiceId } from \"./kernel/ids/service.js\";\nimport { type FeatureEntry, type ServiceEntry } from \"./repo/entries.js\";\nimport { featureSpecServices, listFeatures, listFleetTree, serviceEntries } from \"./repo/repo.js\";\nimport type { FleetTree } from \"./repo/tree/walk.js\";\nimport { parseRequirements } from \"./document/parse.js\";\nimport { type Requirement } from \"./document/spec.js\";\nimport type { DocsDir, FeatureDir } from \"./kernel/ids/dirs.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n /**\n * Reads of a service's `asyncapi.yaml`. Counted separately because this axis\n * is the one that makes a per-service run read the whole fleet: \"does anybody\n * publish this message\" has no local answer, so `spine.message-unproduced`\n * walks every service's contract. The memo is what keeps that one walk per\n * command instead of one per consuming edge.\n */\n asyncapiParses: number;\n /**\n * Reads of the capability vocabulary — `architecture/capabilities.yaml` AND\n * the `capabilities/` walk, which are one vocabulary and so one read. One per\n * invocation is the capability axis's exit criterion, and this counter is how\n * a test pins it. (Permissions predates the memo and re-parses per service —\n * do not copy that.)\n */\n capabilityParses: number;\n /**\n * Walks of ONE feature's `capabilities/` delta tree. Counted apart from\n * `capabilityParses` because it answers a different question about a\n * different directory, and because a fleet that has not adopted the business\n * axis must be able to prove it pays nothing: every feature's walk is a\n * single `existsSync` that finds no directory, so this counter rises while\n * `textReads` and `requirementParses` do not.\n */\n featureCapabilityWalks: number;\n likec4Loads: number;\n /**\n * PER-SERVICE PROJECTS parsed — the map plus one extending model, staged\n * together (`c4/service-model/`). Apart from `likec4Loads` because they are\n * different units: that counter means documents parsed alone, and a fleet\n * whose models all extend the map moves every unit to this one. One project\n * per service is the axis's exit criterion, and this is how a test pins it.\n */\n projectLoads: number;\n}\n\nconst key = (path: string): string => resolve(path);\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly trees = new Map<string, Promise<FleetTree>>();\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<RawServiceId[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly asyncapis = new Map<string, Promise<AsyncapiDoc>>();\n private readonly capabilityVocabularies = new Map<string, Promise<CapabilityVocabulary>>();\n private readonly featureCapabilities = new Map<string, Promise<CapabilityTree>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n /**\n * The service-model axis's own memos, one object because the rules they\n * encode are `c4/prefetch/fleet.ts`'s rather than this class's. It counts\n * through this instance's counter, so `stats()` stays the one place a caller\n * reads what an invocation cost.\n */\n private readonly models = new ServiceModels({\n onProjectLoad: () => {\n this.counts.projectLoads += 1;\n },\n known: async (docsDir) => new Set((await this.listServices(docsDir)).map((s) => s.id)),\n standalone: (path) => this.loadLikeC4(path),\n });\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n asyncapiParses: 0,\n capabilityParses: 0,\n featureCapabilityWalks: 0,\n likec4Loads: 0,\n projectLoads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n /**\n * `memo`, counting the miss under `counter` — every read below is this one\n * call. The count is taken when the load STARTS, inside the miss, so a\n * second asker of an in-flight read is a hit and is never counted: the\n * counters mean \"work this invocation paid for\", which is what the stats\n * tests pin per axis.\n */\n private counted<T>(\n store: Map<string, Promise<T>>,\n k: string,\n counter: keyof FleetContextStats,\n make: () => Promise<T>,\n ): Promise<T> {\n return memo(store, k, () => {\n this.counts[counter] += 1;\n return make();\n });\n }\n\n /**\n * The classified `services/` tree — the ONE enumeration this invocation\n * performs. `listServices` below derives its entries from this memo, so a\n * command that asks for both the fleet and the tree's findings (`validate\n * --all` does) still walks the directories exactly once. The counter keeps\n * its historical meaning — walks of `services/` — it just counts the walk\n * where the walk now happens.\n */\n fleetTree(docsDir: DocsDir): Promise<FleetTree> {\n return this.counted(this.trees, key(docsDir), \"serviceEnumerations\", () => listFleetTree(docsDir));\n }\n\n listServices(docsDir: DocsDir): Promise<ServiceEntry[]> {\n return memo(this.services, key(docsDir), () => this.fleetTree(docsDir).then(serviceEntries));\n }\n\n listFeatures(docsDir: DocsDir, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n return this.counted(this.features, k, \"featureEnumerations\", () =>\n listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n }),\n );\n }\n\n featureSpecServices(featureDir: FeatureDir): Promise<RawServiceId[]> {\n return this.counted(this.featureServices, key(featureDir), \"featureServiceEnumerations\", () =>\n featureSpecServices(featureDir),\n );\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n return this.counted(this.texts, key(path), \"textReads\", () =>\n readFile(path).then((bytes) => decodeDocument(bytes, path)),\n );\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n return this.counted(this.requirements, key(path), \"requirementParses\", () =>\n this.readText(path).then(parseRequirements),\n );\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n return this.counted(this.openapis, key(path), \"openapiParses\", () => readOpenapi(path));\n }\n\n readAsyncapi(path: string): Promise<AsyncapiDoc> {\n return this.counted(this.asyncapis, key(path), \"asyncapiParses\", () => readAsyncapi(path));\n }\n\n /**\n * The capability vocabulary, read once per invocation. Direction rule,\n * load-bearing: this file imports `capabilities/capabilities.js`, so nothing\n * under src/core/capabilities/ may import this module back — that edge is a\n * core-root↔capabilities package cycle `import/no-cycle` cannot see, which is\n * why the functions there take plain data or a `read` function, never a FleetContext.\n */\n capabilities(docsDir: DocsDir): Promise<CapabilityVocabulary> {\n return this.counted(this.capabilityVocabularies, key(docsDir), \"capabilityParses\", () =>\n readCapabilityVocabulary(docsDir),\n );\n }\n\n /**\n * One feature's `capabilities/` delta tree, walked once per invocation. Two\n * readers ask for it in the same run — the delta-shape walk\n * (`core/delta/delta.ts`) and the capability overlay\n * (`core/coherence/declared.ts`) — and without the memo a feature carrying\n * three capability deltas paid the readdirs twice.\n *\n * The direction rule stated on `capabilities()` above applies verbatim: the\n * function this stands in front of takes a plain `FeatureDir`, never a\n * FleetContext, so `src/core/capabilities/` never imports this module back.\n */\n featureCapabilityDeltas(featureDir: FeatureDir): Promise<CapabilityTree> {\n return this.counted(this.featureCapabilities, key(featureDir), \"featureCapabilityWalks\", () =>\n featureCapabilityDeltas(featureDir),\n );\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi/doc.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n return this.counted(this.likec4, key(path), \"likec4Loads\", () => loadFile(path));\n }\n\n /**\n * Parse many `.likec4` documents through ONE shared workspace (`loadBatch`)\n * and seed the memo above, so every later `loadLikeC4` in this invocation is\n * a hit. Purely an accelerator: the cache is still this instance's — one\n * invocation, one snapshot, no module state — and the counter still means\n * \"documents parsed via LikeC4\", incremented once per batch-parsed document.\n *\n * The failure story is the contract here. A batch-INFRASTRUCTURE failure —\n * a sandboxed runner denying tmpdir writes is the environment class ROADMAP\n * documents — degrades to today's per-path loads by seeding nothing: never\n * an error, and never fewer findings, only the old speed. A path the batch\n * could not stage (missing, unreadable) is simply absent from the result, so\n * the later `loadLikeC4` reproduces today's per-path error exactly. And\n * fewer than two misses returns without a workspace at all: one document\n * gains nothing from batch isolation it already has.\n */\n async prefetchLikeC4(paths: string[]): Promise<void> {\n const missing = [...new Set(paths.map(key))].filter((k) => !this.likec4.has(k));\n if (missing.length < 2) return;\n let docs: Map<string, LoadedDoc>;\n try {\n docs = await loadBatch(missing);\n } catch {\n return;\n }\n for (const [k, doc] of docs) {\n // Keyed by the same `key()` as loadLikeC4 — loadBatch resolves paths the\n // identical way, and `missing` was already resolved, so this holds by\n // construction; the has() guard keeps a concurrent load's promise\n // authoritative rather than double-counting the parse.\n if (this.likec4.has(k)) continue;\n this.counts.likec4Loads += 1;\n this.likec4.set(k, Promise.resolve(doc));\n }\n }\n\n /** The fleet map as the PROJECT it is — landscape plus use cases, minus the generated views file. */\n architecture(docsDir: DocsDir): Promise<LoadedDoc> {\n return this.models.architecture(docsDir);\n }\n\n /**\n * Which shape each of these models has, keyed by resolved path — one read of\n * each file's bytes per invocation. Ask over the FULL enumeration, never a\n * narrowed run's subset: shapes decide which directories are owed a\n * per-service `likec4.config.json`, and a partial map answers \"not\n * standalone\" for every service outside it.\n */\n modelShapes(paths: readonly string[]): Promise<Map<string, ModelShape>> {\n return this.models.shapes(paths);\n }\n\n /**\n * A service's C4 model, read the way its own SHAPE demands — the one entry\n * point for every reader of `model.likec4`, standalone or extending\n * (`c4/service-model/load.ts` states the four arms and why each exists).\n */\n serviceModel(docsDir: DocsDir, paths: ServicePaths): Promise<ServiceModel> {\n return this.models.model(docsDir, paths);\n }\n\n /**\n * Every service's model prepared in two workspaces whatever the fleet's mix:\n * the extending ones as one project each in one of them, the standalone ones\n * through the document batch above. An accelerator with `prefetchLikeC4`'s\n * failure story exactly — a batch that could not be created seeds nothing, so\n * findings never change because of a tmpdir, only the speed does.\n */\n async prefetchServiceModels(docsDir: DocsDir, paths: readonly ServicePaths[]): Promise<void> {\n await this.prefetchLikeC4(await this.models.prefetch(docsDir, paths));\n }\n}\n"]}
|
package/dist/core/gate/checks.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* plus the one integrity error that makes freshness
|
|
11
11
|
* unjudgeable (frontmatter.malformed) and a refusing decode
|
|
12
12
|
* of the spec bytes themselves
|
|
13
|
-
* verification verificationState per
|
|
13
|
+
* verification verificationState per feature touching the service — the
|
|
14
|
+
* active ones, plus those archived after `--since <ref>` when
|
|
15
|
+
* a pipeline names the docs commit it last deployed from
|
|
14
16
|
* (its own module, `./verification.ts`)
|
|
15
17
|
* interrupted the `.loam-commit` journal — the one repo state that poisons
|
|
16
18
|
* every other answer above
|
|
@@ -38,7 +40,7 @@ export async function gateReport(req) {
|
|
|
38
40
|
const scan = await partnerScan({ docsDir: req.docsDir, service: req.service, fleet: req.fleet });
|
|
39
41
|
const [freshness, verification, interrupted] = await Promise.all([
|
|
40
42
|
freshnessFindings(req, scan),
|
|
41
|
-
verificationCheck(
|
|
43
|
+
verificationCheck(req),
|
|
42
44
|
interruptedCommitFinding(req.docsDir),
|
|
43
45
|
]);
|
|
44
46
|
const checks = [
|
|
@@ -49,6 +51,7 @@ export async function gateReport(req) {
|
|
|
49
51
|
];
|
|
50
52
|
return {
|
|
51
53
|
service: req.service,
|
|
54
|
+
since: req.since === undefined ? null : { ref: req.since.ref, commit: req.since.commit },
|
|
52
55
|
landscape: scan.landscape,
|
|
53
56
|
partners: scan.partners,
|
|
54
57
|
features: verification.features,
|
|
@@ -111,7 +114,11 @@ function partnersFindings(scan, service) {
|
|
|
111
114
|
for (const p of scan.partners) {
|
|
112
115
|
// An `#external` partner is somebody else's system: its side of the join
|
|
113
116
|
// is unrecorded ON PURPOSE, the same exemption the landscape census grants
|
|
114
|
-
// (`landscape.service-undocumented` skips #external too).
|
|
117
|
+
// (`landscape.service-undocumented` skips #external too). The fleet's own
|
|
118
|
+
// infrastructure — a `database`, `queue`, `topic`, `cache` or `bucket` — is
|
|
119
|
+
// not a partner at all and never reaches this loop: `./partners.ts` drops it
|
|
120
|
+
// from the SET, beside the actors, because the three remedies below are all
|
|
121
|
+
// false of a store the fleet runs.
|
|
115
122
|
if (p.external)
|
|
116
123
|
continue;
|
|
117
124
|
if (p.maturity === null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../../src/core/gate/checks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The four checks behind `loam gate` — a can-i-deploy-shaped PURE QUERY over\n * evidence other commands already recorded. Nothing here executes anything,\n * writes anything, takes the docs lock, or re-derives a coherence plan: every\n * answer is either already on disk (verification.yaml, vouch stamps, the\n * commit journal) or a presence/landscape fact the enumeration carries.\n *\n * partners the landscape's direct joins, both ends graded on the ladder\n * freshness content.stale / sources.stale over the target and partners,\n * plus the one integrity error that makes freshness\n * unjudgeable (frontmatter.malformed) and a refusing decode\n * of the spec bytes themselves\n * verification verificationState per active feature touching the service\n * (its own module, `./verification.ts`)\n * interrupted the `.loam-commit` journal — the one repo state that poisons\n * every other answer above\n *\n * Every `gate.*` code is spelled as a literal `code:` property at its emit\n * site, never through a code-taking factory parameter — verifyStep\n * (core/status/verification.ts) records that test/codes-drift.test.ts's\n * collector is blind to the parameter form, and a refactor toward a factory\n * would silently undocument the family.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { serviceProvenance } from \"../provenance/findings.js\";\nimport { interruptedCommitFinding } from \"../staging/recovery/finding.js\";\nimport { servicePathsAt, SPEC_AXES, type ServicePaths } from \"../repo/paths.js\";\nimport { serviceTreePath } from \"../kernel/ids/dirs.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport { atLeastDocumented, partnerScan, type PartnerScan } from \"./partners.js\";\nimport { verificationCheck } from \"./verification.js\";\nimport { unreadableSubject, type GateCheck, type GateReport } from \"./report.js\";\n\nexport interface GateRequest {\n docsDir: DocsDir;\n /** The service being deployed — a name the enumeration approved. */\n service: RawServiceId;\n /**\n * The repository loam is standing in, passed ONLY when the gated service is\n * this repo's own binding — validate's `repoOf` rule: `sources` are paths\n * into a service's own repository, so `sources.stale` is honestly checkable\n * only there, while `content.stale` needs nothing but the document.\n */\n repoDir?: string;\n fleet: FleetContext;\n}\n\nexport async function gateReport(req: GateRequest): Promise<GateReport> {\n // The scan first, alone: freshness walks the partner set it derives. The\n // three remaining checks are independent of each other — different files,\n // memoized reads — so they run concurrently; only the scan must be ordered.\n const scan = await partnerScan({ docsDir: req.docsDir, service: req.service, fleet: req.fleet });\n const [freshness, verification, interrupted] = await Promise.all([\n freshnessFindings(req, scan),\n verificationCheck({ docsDir: req.docsDir, service: req.service, fleet: req.fleet }),\n interruptedCommitFinding(req.docsDir),\n ]);\n const checks: GateCheck[] = [\n { check: \"partners\", findings: partnersFindings(scan, req.service) },\n { check: \"freshness\", findings: freshness },\n { check: \"verification\", findings: verification.findings },\n { check: \"interrupted\", findings: interrupted === null ? [] : [interrupted] },\n ];\n return {\n service: req.service,\n landscape: scan.landscape,\n partners: scan.partners,\n features: verification.features,\n checks,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Check 1: partners */\n/* ------------------------------------------------------------------ */\n\nfunction partnersFindings(scan: PartnerScan, service: string): Finding[] {\n const findings: Finding[] = [];\n // \"Could not look\" must never read as \"no partners\" — the docsRepoReady\n // doctrine one level down. `validate --all` owns the diagnosis of the\n // landscape itself; this code only refuses the false silence. The two ways\n // of not looking are graded apart, the way `landscape.missing` and\n // `landscape.invalid` are: ABSENT is a warning — a repo before its first\n // adopt legitimately has no map yet — but a map that EXISTS and cannot be\n // parsed or read is an ERROR, because `validate --all` fails that repo\n // (`landscape.invalid`) and a deploy gate that passed where the fleet gate\n // fails would be the quieter of two contradictory verdicts.\n if (scan.landscape === \"absent\") {\n findings.push({\n severity: \"warn\",\n code: \"gate.partners-unknown\",\n message: `architecture/landscape.likec4 does not exist, so the partner set for '${service}' could not be derived — an absent map means nobody could look, not that nothing joins this service. \\`loam validate --all\\` diagnoses the landscape itself.`,\n });\n } else if (scan.landscape === \"invalid\") {\n findings.push({\n severity: \"error\",\n code: \"gate.partners-unknown\",\n message:\n `architecture/landscape.likec4 exists but cannot be used — ${scan.landscapeProblem ?? \"it did not parse\"} — so the partner set for '${service}' could not be derived. ` +\n `A broken map means nobody could look, not that nothing joins this service, and \\`loam validate --all\\` fails this repo for the same file. Fix the landscape, then re-run.`,\n });\n }\n // The rung depends on the landscape only through the api question (does\n // anything call this service?). With no readable map that question is\n // unanswerable, and the fail-closed grading — `landscapeEvidence`: no proof\n // means an API is expected — would convict a worker or UI for a contract\n // nobody may be owed. One unanswerable fact yields ONE finding (the\n // landscape's own, above), so under an unreadable map this error fires only\n // when the service is below `documented` REGARDLESS of the api question.\n const target = scan.landscape === \"read\" ? scan.target : scan.targetWithoutApi;\n if (!atLeastDocumented(target.maturity)) {\n findings.push({\n severity: \"error\",\n code: \"gate.service-undocumented\",\n subject: service,\n message:\n `${serviceTreePath(scan.targetEntry)}/ sits at '${target.maturity}' on the adoption ladder — below 'documented', the docs cannot say what its joins even are, so no recorded evidence can answer the deploy question. ` +\n `Author the baseline (\\`loam adopt --service ${service}\\` briefs it); this check passes once the required artifact set exists.`,\n details: target.gaps,\n text: { detailPrefix: \"- \" },\n });\n }\n // The partners that HAVE a directory, by id: their findings name it, and it\n // is `services/<subsystem>/<id>/` for a filed one. The no-directory branch\n // below keeps the bare `services/<id>/` spelling deliberately — there is no\n // directory to name, and the unfiled form is what the fix would create.\n const partnerPath = new Map(scan.partnerEntries.map((e) => [e.id as string, serviceTreePath(e)]));\n for (const p of scan.partners) {\n // An `#external` partner is somebody else's system: its side of the join\n // is unrecorded ON PURPOSE, the same exemption the landscape census grants\n // (`landscape.service-undocumented` skips #external too).\n if (p.external) continue;\n if (p.maturity === null) {\n findings.push({\n severity: \"warn\",\n code: \"gate.partner-undocumented\",\n subject: p.service,\n message:\n `'${p.service}' joins '${service}' (${p.via.join(\", \")}) but has no services/${p.service}/ directory — its side of the join is unrecorded. ` +\n `Adopt it, bind its landscape element to the right service with metadata { service '<id>' }, or tag it #external if it is not ours.`,\n details: p.via,\n });\n } else if (!atLeastDocumented(p.maturity)) {\n findings.push({\n severity: \"warn\",\n code: \"gate.partner-undocumented\",\n subject: p.service,\n message:\n `${partnerPath.get(p.service) ?? `services/${p.service}`}/ sits at '${p.maturity}' — below 'documented' — while it joins '${service}' (${p.via.join(\", \")}): its side of the join is not fully recorded. \\`loam adopt --service ${p.service}\\` briefs what is missing.`,\n details: p.via,\n });\n }\n }\n return findings;\n}\n\n/* ------------------------------------------------------------------ */\n/* Check 2: freshness */\n/* ------------------------------------------------------------------ */\n\n/**\n * `serviceProvenance` filtered to the two staleness codes plus the ONE\n * integrity error that makes freshness unjudgeable: `frontmatter.malformed`.\n * A header YAML refuses to parse cannot certify a `content_digest`, and\n * dropping that error read \"cannot judge\" as \"nothing stale\" — the exact\n * fail-open a deploy gate must not have. Everything else provenance grades\n * (missing owners, unstamped sources, absent frontmatter) is `validate`'s\n * report to make — a deploy gate that repeated it would drown the findings a\n * pipeline can actually act on at deploy time.\n */\nasync function freshnessFindings(req: GateRequest, scan: PartnerScan): Promise<Finding[]> {\n const subjects: { id: RawServiceId; repoDir: string | undefined; paths: ServicePaths }[] = [\n {\n id: req.service,\n repoDir: req.repoDir,\n paths: await locateServicePaths(req.docsDir, req.service, req.fleet),\n },\n // Only the DOCUMENTED partners: a partner with no directory has nothing to\n // grade, and already carries gate.partner-undocumented from check 1.\n ...scan.partnerEntries.map((e) => ({\n id: e.id,\n repoDir: undefined,\n paths: servicePathsAt(e.dir),\n })),\n ];\n // Independent per-subject reads through the shared memo; inOrder keeps the\n // findings in subject order (target first, partners sorted) so identical\n // state yields identical bytes.\n const graded = await inOrder(subjects, async (s) => {\n try {\n // The integrity gate serviceProvenance cannot give: its own reads decode\n // with 'utf8', which silently turns UTF-16 bytes into replacement\n // characters — the file then grades as having no frontmatter at all (a\n // warning this check does not retain), and mangled bytes read as\n // \"nothing stale\". So both spec axes are decoded FIRST through the\n // invocation's read index, whose decoder refuses non-UTF-8 naming the\n // file (NotUtf8DocumentError spells `path` the way Node does, so the\n // containment below names it without knowing the type).\n for (const axis of SPEC_AXES) {\n const path = s.paths[axis.key];\n if (existsSync(path)) await req.fleet.readText(path);\n }\n const all = await serviceProvenance(req.docsDir, s.id, { repoDir: s.repoDir, fleet: req.fleet });\n return all\n .filter(\n (f) =>\n f.code === \"content.stale\" ||\n f.code === \"sources.stale\" ||\n f.code === \"frontmatter.malformed\",\n )\n // The staleness emitters name the file in the message but set no\n // subject; the payload's per-service attribution should not make a\n // caller parse it back out of prose.\n .map((f) => (f.subject === undefined ? { ...f, subject: s.id } : f));\n } catch (err) {\n return [unreadableSubject(\"service\", s.id, err)];\n }\n });\n return graded.flat();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../../src/core/gate/checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,SAAS,EAAqB,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAK/D,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAoB,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAmD,MAAM,aAAa,CAAC;AAkBjG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAgB;IAC/C,yEAAyE;IACzE,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACjG,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC/D,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;QAC5B,iBAAiB,CAAC,GAAG,CAAC;QACtB,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC;KACtC,CAAC,CAAC;IACH,MAAM,MAAM,GAAgB;QAC1B,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE;QACpE,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE;QAC3C,EAAE,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,QAAQ,EAAE;QAC1D,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE;KAC9E,CAAC;IACF,OAAO;QACL,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE;QACxF,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,MAAM;KACP,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,SAAS,gBAAgB,CAAC,IAAiB,EAAE,OAAe;IAC1D,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,wEAAwE;IACxE,sEAAsE;IACtE,2EAA2E;IAC3E,mEAAmE;IACnE,yEAAyE;IACzE,0EAA0E;IAC1E,uEAAuE;IACvE,2EAA2E;IAC3E,4DAA4D;IAC5D,IAAI,IAAI,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EAAE,yEAAyE,OAAO,8JAA8J;SACxP,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,uBAAuB;YAC7B,OAAO,EACL,6DAA6D,IAAI,CAAC,gBAAgB,IAAI,kBAAkB,8BAA8B,OAAO,0BAA0B;gBACvK,2KAA2K;SAC9K,CAAC,CAAC;IACL,CAAC;IACD,wEAAwE;IACxE,sEAAsE;IACtE,4EAA4E;IAC5E,yEAAyE;IACzE,oEAAoE;IACpE,4EAA4E;IAC5E,yEAAyE;IACzE,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC;IAC/E,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,2BAA2B;YACjC,OAAO,EAAE,OAAO;YAChB,OAAO,EACL,GAAG,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,MAAM,CAAC,QAAQ,sJAAsJ;gBACvN,+CAA+C,OAAO,yEAAyE;YACjI,OAAO,EAAE,MAAM,CAAC,IAAI;YACpB,IAAI,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;SAC7B,CAAC,CAAC;IACL,CAAC;IACD,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,wEAAwE;IACxE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAY,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClG,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,yEAAyE;QACzE,2EAA2E;QAC3E,0EAA0E;QAC1E,4EAA4E;QAC5E,6EAA6E;QAC7E,4EAA4E;QAC5E,mCAAmC;QACnC,IAAI,CAAC,CAAC,QAAQ;YAAE,SAAS;QACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,2BAA2B;gBACjC,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,OAAO,EACL,IAAI,CAAC,CAAC,OAAO,YAAY,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,OAAO,oDAAoD;oBAC5I,oIAAoI;gBACtI,OAAO,EAAE,CAAC,CAAC,GAAG;aACf,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,2BAA2B;gBACjC,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,OAAO,EACL,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,QAAQ,4CAA4C,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC,OAAO,4BAA4B;gBACzQ,OAAO,EAAE,CAAC,CAAC,GAAG;aACf,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE;;;;;;;;;GASG;AACH,KAAK,UAAU,iBAAiB,CAAC,GAAgB,EAAE,IAAiB;IAClE,MAAM,QAAQ,GAA6E;QACzF;YACE,EAAE,EAAE,GAAG,CAAC,OAAO;YACf,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,MAAM,kBAAkB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC;SACrE;QACD,2EAA2E;QAC3E,qEAAqE;QACrE,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjC,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,OAAO,EAAE,SAAS;YAClB,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC;SAC7B,CAAC,CAAC;KACJ,CAAC;IACF,2EAA2E;IAC3E,yEAAyE;IACzE,gCAAgC;IAChC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjD,IAAI,CAAC;YACH,yEAAyE;YACzE,kEAAkE;YAClE,uEAAuE;YACvE,iEAAiE;YACjE,mEAAmE;YACnE,sEAAsE;YACtE,qEAAqE;YACrE,wDAAwD;YACxD,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC/B,IAAI,UAAU,CAAC,IAAI,CAAC;oBAAE,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACvD,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YACjG,OAAO,GAAG;iBACP,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,KAAK,eAAe;gBAC1B,CAAC,CAAC,IAAI,KAAK,eAAe;gBAC1B,CAAC,CAAC,IAAI,KAAK,uBAAuB,CACrC;gBACD,iEAAiE;gBACjE,mEAAmE;gBACnE,qCAAqC;iBACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,iBAAiB,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC","sourcesContent":["/**\n * The four checks behind `loam gate` — a can-i-deploy-shaped PURE QUERY over\n * evidence other commands already recorded. Nothing here executes anything,\n * writes anything, takes the docs lock, or re-derives a coherence plan: every\n * answer is either already on disk (verification.yaml, vouch stamps, the\n * commit journal) or a presence/landscape fact the enumeration carries.\n *\n * partners the landscape's direct joins, both ends graded on the ladder\n * freshness content.stale / sources.stale over the target and partners,\n * plus the one integrity error that makes freshness\n * unjudgeable (frontmatter.malformed) and a refusing decode\n * of the spec bytes themselves\n * verification verificationState per feature touching the service — the\n * active ones, plus those archived after `--since <ref>` when\n * a pipeline names the docs commit it last deployed from\n * (its own module, `./verification.ts`)\n * interrupted the `.loam-commit` journal — the one repo state that poisons\n * every other answer above\n *\n * Every `gate.*` code is spelled as a literal `code:` property at its emit\n * site, never through a code-taking factory parameter — verifyStep\n * (core/status/verification.ts) records that test/codes-drift.test.ts's\n * collector is blind to the parameter form, and a refactor toward a factory\n * would silently undocument the family.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { serviceProvenance } from \"../provenance/findings.js\";\nimport { interruptedCommitFinding } from \"../staging/recovery/finding.js\";\nimport { servicePathsAt, SPEC_AXES, type ServicePaths } from \"../repo/paths.js\";\nimport { serviceTreePath } from \"../kernel/ids/dirs.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { RawServiceId } from \"../kernel/ids/service.js\";\nimport { atLeastDocumented, partnerScan, type PartnerScan } from \"./partners.js\";\nimport { verificationCheck } from \"./verification.js\";\nimport { unreadableSubject, type GateCheck, type GateReport, type GateSince } from \"./report.js\";\n\nexport interface GateRequest {\n docsDir: DocsDir;\n /** The service being deployed — a name the enumeration approved. */\n service: RawServiceId;\n /**\n * The repository loam is standing in, passed ONLY when the gated service is\n * this repo's own binding — validate's `repoOf` rule: `sources` are paths\n * into a service's own repository, so `sources.stale` is honestly checkable\n * only there, while `content.stale` needs nothing but the document.\n */\n repoDir?: string;\n fleet: FleetContext;\n /** `--since <ref>`, resolved by the command; absent means in-flight features only. */\n since?: GateSince;\n}\n\nexport async function gateReport(req: GateRequest): Promise<GateReport> {\n // The scan first, alone: freshness walks the partner set it derives. The\n // three remaining checks are independent of each other — different files,\n // memoized reads — so they run concurrently; only the scan must be ordered.\n const scan = await partnerScan({ docsDir: req.docsDir, service: req.service, fleet: req.fleet });\n const [freshness, verification, interrupted] = await Promise.all([\n freshnessFindings(req, scan),\n verificationCheck(req),\n interruptedCommitFinding(req.docsDir),\n ]);\n const checks: GateCheck[] = [\n { check: \"partners\", findings: partnersFindings(scan, req.service) },\n { check: \"freshness\", findings: freshness },\n { check: \"verification\", findings: verification.findings },\n { check: \"interrupted\", findings: interrupted === null ? [] : [interrupted] },\n ];\n return {\n service: req.service,\n since: req.since === undefined ? null : { ref: req.since.ref, commit: req.since.commit },\n landscape: scan.landscape,\n partners: scan.partners,\n features: verification.features,\n checks,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Check 1: partners */\n/* ------------------------------------------------------------------ */\n\nfunction partnersFindings(scan: PartnerScan, service: string): Finding[] {\n const findings: Finding[] = [];\n // \"Could not look\" must never read as \"no partners\" — the docsRepoReady\n // doctrine one level down. `validate --all` owns the diagnosis of the\n // landscape itself; this code only refuses the false silence. The two ways\n // of not looking are graded apart, the way `landscape.missing` and\n // `landscape.invalid` are: ABSENT is a warning — a repo before its first\n // adopt legitimately has no map yet — but a map that EXISTS and cannot be\n // parsed or read is an ERROR, because `validate --all` fails that repo\n // (`landscape.invalid`) and a deploy gate that passed where the fleet gate\n // fails would be the quieter of two contradictory verdicts.\n if (scan.landscape === \"absent\") {\n findings.push({\n severity: \"warn\",\n code: \"gate.partners-unknown\",\n message: `architecture/landscape.likec4 does not exist, so the partner set for '${service}' could not be derived — an absent map means nobody could look, not that nothing joins this service. \\`loam validate --all\\` diagnoses the landscape itself.`,\n });\n } else if (scan.landscape === \"invalid\") {\n findings.push({\n severity: \"error\",\n code: \"gate.partners-unknown\",\n message:\n `architecture/landscape.likec4 exists but cannot be used — ${scan.landscapeProblem ?? \"it did not parse\"} — so the partner set for '${service}' could not be derived. ` +\n `A broken map means nobody could look, not that nothing joins this service, and \\`loam validate --all\\` fails this repo for the same file. Fix the landscape, then re-run.`,\n });\n }\n // The rung depends on the landscape only through the api question (does\n // anything call this service?). With no readable map that question is\n // unanswerable, and the fail-closed grading — `landscapeEvidence`: no proof\n // means an API is expected — would convict a worker or UI for a contract\n // nobody may be owed. One unanswerable fact yields ONE finding (the\n // landscape's own, above), so under an unreadable map this error fires only\n // when the service is below `documented` REGARDLESS of the api question.\n const target = scan.landscape === \"read\" ? scan.target : scan.targetWithoutApi;\n if (!atLeastDocumented(target.maturity)) {\n findings.push({\n severity: \"error\",\n code: \"gate.service-undocumented\",\n subject: service,\n message:\n `${serviceTreePath(scan.targetEntry)}/ sits at '${target.maturity}' on the adoption ladder — below 'documented', the docs cannot say what its joins even are, so no recorded evidence can answer the deploy question. ` +\n `Author the baseline (\\`loam adopt --service ${service}\\` briefs it); this check passes once the required artifact set exists.`,\n details: target.gaps,\n text: { detailPrefix: \"- \" },\n });\n }\n // The partners that HAVE a directory, by id: their findings name it, and it\n // is `services/<subsystem>/<id>/` for a filed one. The no-directory branch\n // below keeps the bare `services/<id>/` spelling deliberately — there is no\n // directory to name, and the unfiled form is what the fix would create.\n const partnerPath = new Map(scan.partnerEntries.map((e) => [e.id as string, serviceTreePath(e)]));\n for (const p of scan.partners) {\n // An `#external` partner is somebody else's system: its side of the join\n // is unrecorded ON PURPOSE, the same exemption the landscape census grants\n // (`landscape.service-undocumented` skips #external too). The fleet's own\n // infrastructure — a `database`, `queue`, `topic`, `cache` or `bucket` — is\n // not a partner at all and never reaches this loop: `./partners.ts` drops it\n // from the SET, beside the actors, because the three remedies below are all\n // false of a store the fleet runs.\n if (p.external) continue;\n if (p.maturity === null) {\n findings.push({\n severity: \"warn\",\n code: \"gate.partner-undocumented\",\n subject: p.service,\n message:\n `'${p.service}' joins '${service}' (${p.via.join(\", \")}) but has no services/${p.service}/ directory — its side of the join is unrecorded. ` +\n `Adopt it, bind its landscape element to the right service with metadata { service '<id>' }, or tag it #external if it is not ours.`,\n details: p.via,\n });\n } else if (!atLeastDocumented(p.maturity)) {\n findings.push({\n severity: \"warn\",\n code: \"gate.partner-undocumented\",\n subject: p.service,\n message:\n `${partnerPath.get(p.service) ?? `services/${p.service}`}/ sits at '${p.maturity}' — below 'documented' — while it joins '${service}' (${p.via.join(\", \")}): its side of the join is not fully recorded. \\`loam adopt --service ${p.service}\\` briefs what is missing.`,\n details: p.via,\n });\n }\n }\n return findings;\n}\n\n/* ------------------------------------------------------------------ */\n/* Check 2: freshness */\n/* ------------------------------------------------------------------ */\n\n/**\n * `serviceProvenance` filtered to the two staleness codes plus the ONE\n * integrity error that makes freshness unjudgeable: `frontmatter.malformed`.\n * A header YAML refuses to parse cannot certify a `content_digest`, and\n * dropping that error read \"cannot judge\" as \"nothing stale\" — the exact\n * fail-open a deploy gate must not have. Everything else provenance grades\n * (missing owners, unstamped sources, absent frontmatter) is `validate`'s\n * report to make — a deploy gate that repeated it would drown the findings a\n * pipeline can actually act on at deploy time.\n */\nasync function freshnessFindings(req: GateRequest, scan: PartnerScan): Promise<Finding[]> {\n const subjects: { id: RawServiceId; repoDir: string | undefined; paths: ServicePaths }[] = [\n {\n id: req.service,\n repoDir: req.repoDir,\n paths: await locateServicePaths(req.docsDir, req.service, req.fleet),\n },\n // Only the DOCUMENTED partners: a partner with no directory has nothing to\n // grade, and already carries gate.partner-undocumented from check 1.\n ...scan.partnerEntries.map((e) => ({\n id: e.id,\n repoDir: undefined,\n paths: servicePathsAt(e.dir),\n })),\n ];\n // Independent per-subject reads through the shared memo; inOrder keeps the\n // findings in subject order (target first, partners sorted) so identical\n // state yields identical bytes.\n const graded = await inOrder(subjects, async (s) => {\n try {\n // The integrity gate serviceProvenance cannot give: its own reads decode\n // with 'utf8', which silently turns UTF-16 bytes into replacement\n // characters — the file then grades as having no frontmatter at all (a\n // warning this check does not retain), and mangled bytes read as\n // \"nothing stale\". So both spec axes are decoded FIRST through the\n // invocation's read index, whose decoder refuses non-UTF-8 naming the\n // file (NotUtf8DocumentError spells `path` the way Node does, so the\n // containment below names it without knowing the type).\n for (const axis of SPEC_AXES) {\n const path = s.paths[axis.key];\n if (existsSync(path)) await req.fleet.readText(path);\n }\n const all = await serviceProvenance(req.docsDir, s.id, { repoDir: s.repoDir, fleet: req.fleet });\n return all\n .filter(\n (f) =>\n f.code === \"content.stale\" ||\n f.code === \"sources.stale\" ||\n f.code === \"frontmatter.malformed\",\n )\n // The staleness emitters name the file in the message but set no\n // subject; the payload's per-service attribution should not make a\n // caller parse it back out of prose.\n .map((f) => (f.subject === undefined ? { ...f, subject: s.id } : f));\n } catch (err) {\n return [unreadableSubject(\"service\", s.id, err)];\n }\n });\n return graded.flat();\n}\n"]}
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* letting it read as "no partners".
|
|
14
14
|
*/
|
|
15
15
|
import { existsSync } from "node:fs";
|
|
16
|
-
import {} from "../c4/likec4.js";
|
|
16
|
+
import { errorText } from "../c4/likec4.js";
|
|
17
17
|
import { serviceResolver } from "../c4/resolve/service.js";
|
|
18
|
-
import { ACTOR_KINDS, EXTERNAL_TAG, landscapeEvidence, maturityGaps, serviceMaturity, MATURITY_LADDER, } from "../vocabulary/maturity.js";
|
|
18
|
+
import { ACTOR_KINDS, EXTERNAL_TAG, INFRA_KINDS, landscapeEvidence, maturityGaps, serviceMaturity, MATURITY_LADDER, } from "../vocabulary/maturity.js";
|
|
19
19
|
import { compareIds } from "../repo/entries.js";
|
|
20
20
|
import { landscapePath, servicePathsAt } from "../repo/paths.js";
|
|
21
21
|
import { enumeratedServices } from "../repo/service-target.js";
|
|
@@ -25,19 +25,36 @@ export function atLeastDocumented(m) {
|
|
|
25
25
|
return MATURITY_LADDER.indexOf(m) >= MATURITY_LADDER.indexOf("documented");
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* What the map says about one edge END: does it stand for a person,
|
|
29
|
-
* tagged `#external`? Walked up the ancestor chain
|
|
30
|
-
* usually written on the containing system while
|
|
31
|
-
* nested element — but the walk STOPS at the first
|
|
32
|
-
* service (an explicit binding or a title naming a
|
|
33
|
-
* `serviceResolver`'s own first two rungs, checked after that
|
|
34
|
-
* facts are read). What sits above the service boundary is a
|
|
35
|
-
* grouping's `#external` is the grouping's claim, not the
|
|
36
|
-
* the stop, an adopted service drawn inside an `#external`
|
|
37
|
-
* from `gate.partner-undocumented` and its real rung hidden.
|
|
28
|
+
* What the map says about one edge END: does it stand for a person, is it
|
|
29
|
+
* INFRASTRUCTURE, and is it tagged `#external`? Walked up the ancestor chain
|
|
30
|
+
* because all three facts are usually written on the containing system while
|
|
31
|
+
* the edge is drawn from a nested element — but the walk STOPS at the first
|
|
32
|
+
* element that stands for a service (an explicit binding or a title naming a
|
|
33
|
+
* real directory, `serviceResolver`'s own first two rungs, checked after that
|
|
34
|
+
* element's own facts are read). What sits above the service boundary is a
|
|
35
|
+
* grouping, and a grouping's `#external` is the grouping's claim, not the
|
|
36
|
+
* service's: without the stop, an adopted service drawn inside an `#external`
|
|
37
|
+
* zone was exempted from `gate.partner-undocumented` and its real rung hidden.
|
|
38
|
+
*
|
|
39
|
+
* `infra` is read only off an element that stands for NO service AND sits
|
|
40
|
+
* inside none, which is the asymmetry the `standsFor` local exists for. That is
|
|
41
|
+
* `commands/validate/fleet/census.ts` in two steps and in its order:
|
|
42
|
+
* `serviceLevelElements` drops everything drawn inside a service BEFORE
|
|
43
|
+
* `drawnSystems` ever applies the kind test, so a service's own interior never
|
|
44
|
+
* reaches it. Both halves are load-bearing. A `database` bound with
|
|
45
|
+
* `metadata { service '<id>' }` is an adopted service that happens to be drawn
|
|
46
|
+
* as a store — its side of the join IS recorded, and dropping it would hide a
|
|
47
|
+
* real partner's rung. And a store drawn INSIDE its owner — `orderService.db`,
|
|
48
|
+
* the ordinary way a team draws a private datastore — is that service's
|
|
49
|
+
* interior: reading the kind off it convicted the WHOLE endpoint as
|
|
50
|
+
* infrastructure, and `partnerScan` then dropped order-service from the partner
|
|
51
|
+
* set, so a service this gate exists to grade vanished because of how somebody
|
|
52
|
+
* drew its storage. The kind is the fallback for an element nothing else
|
|
53
|
+
* answers for, never the override.
|
|
38
54
|
*/
|
|
39
55
|
function endFacts(byId, known, id) {
|
|
40
56
|
let actor = false;
|
|
57
|
+
let infra = false;
|
|
41
58
|
let external = false;
|
|
42
59
|
// The id itself, then each dotted ancestor — the same walk `serviceResolver`
|
|
43
60
|
// resolves through, out of the one module that spells it
|
|
@@ -46,14 +63,26 @@ function endFacts(byId, known, id) {
|
|
|
46
63
|
const e = byId.get(candidate);
|
|
47
64
|
if (e === undefined)
|
|
48
65
|
continue;
|
|
66
|
+
const standsFor = e.service !== undefined || known.has(e.title);
|
|
49
67
|
if (ACTOR_KINDS.has(e.kind.toLowerCase()))
|
|
50
68
|
actor = true;
|
|
69
|
+
if (!standsFor && INFRA_KINDS.has(e.kind.toLowerCase()))
|
|
70
|
+
infra = true;
|
|
51
71
|
if (e.tags.includes(EXTERNAL_TAG))
|
|
52
72
|
external = true;
|
|
53
|
-
if (
|
|
73
|
+
if (standsFor) {
|
|
74
|
+
// Everything the walk has visited so far was drawn INSIDE this service,
|
|
75
|
+
// so nothing visited so far was service-LEVEL and no kind read off it
|
|
76
|
+
// answers for a service — `serviceLevelElements` filtering ahead of the
|
|
77
|
+
// kind test, spelled as a walk. `actor` and `external` are deliberately
|
|
78
|
+
// left standing: a person or an `#external` tag written on a container is
|
|
79
|
+
// still a claim about this endpoint, while the store shape is a claim
|
|
80
|
+
// about the owner's interior.
|
|
81
|
+
infra = false;
|
|
54
82
|
break;
|
|
83
|
+
}
|
|
55
84
|
}
|
|
56
|
-
return { actor, external };
|
|
85
|
+
return { actor, infra, external };
|
|
57
86
|
}
|
|
58
87
|
/** How one edge joins the two ends, in the words the finding and the payload both use. */
|
|
59
88
|
function viaOf(r) {
|
|
@@ -87,9 +116,11 @@ export async function partnerScan(req) {
|
|
|
87
116
|
if (doc.errors.length > 0) {
|
|
88
117
|
landscape = "invalid";
|
|
89
118
|
const first = doc.errors[0];
|
|
119
|
+
// Through `errorText` — the raw message had no line, and a dropped
|
|
120
|
+
// quote's is a parser token list of thousands of lines.
|
|
90
121
|
landscapeProblem =
|
|
91
122
|
`${doc.errors.length} parse error(s)` +
|
|
92
|
-
(first === undefined ? "" : ` — first: ${first
|
|
123
|
+
(first === undefined ? "" : ` — first: ${errorText(first)}`);
|
|
93
124
|
}
|
|
94
125
|
else {
|
|
95
126
|
landscape = "read";
|
|
@@ -158,14 +189,28 @@ export async function partnerScan(req) {
|
|
|
158
189
|
continue;
|
|
159
190
|
if (target === service) {
|
|
160
191
|
const facts = endFacts(byId, known, r.source);
|
|
161
|
-
// A person is never a deploy participant
|
|
162
|
-
// landscape census applies
|
|
163
|
-
|
|
192
|
+
// A person is never a deploy participant, and neither is the fleet's own
|
|
193
|
+
// infrastructure: the two kind exemptions every landscape census applies
|
|
194
|
+
// (drawnSystems, commands/validate/fleet/census.ts), spelled here off the
|
|
195
|
+
// same two sets so a gate and a validate run cannot disagree about what a
|
|
196
|
+
// partner IS.
|
|
197
|
+
//
|
|
198
|
+
// The infra half is the newer one, and it had a measured cost. A
|
|
199
|
+
// `database` the fleet owns and draws at fleet level joined every service
|
|
200
|
+
// that reads it, with `maturity: null` because there is no
|
|
201
|
+
// `services/<title>/` — so `gate.partner-undocumented` told the deployer
|
|
202
|
+
// to "adopt it, bind it … or tag it #external", three remedies none of
|
|
203
|
+
// which is true of a Postgres (measured on a one-service fixture,
|
|
204
|
+
// 2026-09-07). It is dropped from the partner SET rather than exempted
|
|
205
|
+
// from the finding, which is the actor precedent exactly: `partners` is
|
|
206
|
+
// the list of counterparties whose DOCS the gate grades, and a store has
|
|
207
|
+
// none to grade.
|
|
208
|
+
if (!facts.actor && !facts.infra)
|
|
164
209
|
join(source, "consumer", r, facts.external);
|
|
165
210
|
}
|
|
166
211
|
else if (source === service) {
|
|
167
212
|
const facts = endFacts(byId, known, r.target);
|
|
168
|
-
if (!facts.actor)
|
|
213
|
+
if (!facts.actor && !facts.infra)
|
|
169
214
|
join(target, "provider", r, facts.external);
|
|
170
215
|
}
|
|
171
216
|
}
|